From 4d43388cff5a13bc6ff1dbf189b4168af8c10c81 Mon Sep 17 00:00:00 2001 From: sh2 Date: Fri, 2 May 2025 23:43:07 +0800 Subject: [PATCH 01/66] chore: ignore api types in codecov (#5886) Signed-off-by: shawnh2 Signed-off-by: Arko Dasgupta --- .github/codecov.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/codecov.yml b/.github/codecov.yml index 0b409847f6..d8de57d94a 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -15,3 +15,4 @@ ignore: - "test" - "**/*.pb.go" - "**/zz_generated.*.go" + - "api/**/*_types.go" From ea25008fcc93d982a0850fad3e76050f9a9e3c8b Mon Sep 17 00:00:00 2001 From: Karol Szwaj Date: Fri, 2 May 2025 17:45:17 +0200 Subject: [PATCH 02/66] chore/ci: add `go.lint.fmt` target (#5846) * chore/ci: add lint.gofumpt target Signed-off-by: Karol Szwaj * update review Signed-off-by: Karol Szwaj * Add extra flag to gofumpt, move local golanglint fmt target to golang makefile Signed-off-by: Karol Szwaj * add build tags Signed-off-by: Karol Szwaj * fix lint Signed-off-by: Karol Szwaj --------- Signed-off-by: Karol Szwaj Signed-off-by: Arko Dasgupta --- internal/gatewayapi/backendtrafficpolicy.go | 2 +- internal/gatewayapi/resource/fs.go | 2 +- internal/gatewayapi/resource/fs_test.go | 2 +- internal/utils/merge.go | 2 +- test/e2e/tests/metric.go | 2 +- tools/linter/golangci-lint/.golangci.yml | 2 ++ tools/make/golang.mk | 5 +++++ tools/make/lint.mk | 2 +- 8 files changed, 13 insertions(+), 6 deletions(-) diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go index 5253118685..6a6565cd81 100644 --- a/internal/gatewayapi/backendtrafficpolicy.go +++ b/internal/gatewayapi/backendtrafficpolicy.go @@ -494,7 +494,7 @@ func applyTrafficFeatureToRoute(route RouteContext, } } -func mergeBackendTrafficPolicy(routePolicy *egv1a1.BackendTrafficPolicy, gwPolicy *egv1a1.BackendTrafficPolicy) (*egv1a1.BackendTrafficPolicy, error) { +func mergeBackendTrafficPolicy(routePolicy, gwPolicy *egv1a1.BackendTrafficPolicy) (*egv1a1.BackendTrafficPolicy, error) { if routePolicy.Spec.MergeType == nil || gwPolicy == nil { return routePolicy.DeepCopy(), nil } diff --git a/internal/gatewayapi/resource/fs.go b/internal/gatewayapi/resource/fs.go index 09fdcb1ab2..d2385fcaad 100644 --- a/internal/gatewayapi/resource/fs.go +++ b/internal/gatewayapi/resource/fs.go @@ -11,7 +11,7 @@ import ( "io/fs" "time" - "github.com/envoyproxy/gateway" // nolint:goimports + envoygateway "github.com/envoyproxy/gateway" ) var ( diff --git a/internal/gatewayapi/resource/fs_test.go b/internal/gatewayapi/resource/fs_test.go index c8742a02c8..f0198e6d2f 100644 --- a/internal/gatewayapi/resource/fs_test.go +++ b/internal/gatewayapi/resource/fs_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/envoyproxy/gateway" // nolint:goimports + envoygateway "github.com/envoyproxy/gateway" ) func TestOpenAndReadGatewayCRDsFS(t *testing.T) { diff --git a/internal/utils/merge.go b/internal/utils/merge.go index e3a4e1347a..cb5d890dd4 100644 --- a/internal/utils/merge.go +++ b/internal/utils/merge.go @@ -16,7 +16,7 @@ import ( egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" ) -func Merge[T client.Object](original T, patch T, mergeType egv1a1.MergeType) (T, error) { +func Merge[T client.Object](original, patch T, mergeType egv1a1.MergeType) (T, error) { var ( patchedJSON []byte originalJSON []byte diff --git a/test/e2e/tests/metric.go b/test/e2e/tests/metric.go index cbfb848a47..ec1eb2019a 100644 --- a/test/e2e/tests/metric.go +++ b/test/e2e/tests/metric.go @@ -111,7 +111,7 @@ var MetricWorkqueueAndRestclientTest = suite.ConformanceTest{ ) require.NoError(t, err) - verifyMetrics := func(t *testing.T, metricQuery string, metricName string) { + verifyMetrics := func(t *testing.T, metricQuery, metricName string) { httputils.AwaitConvergence( t, suite.TimeoutConfig.RequiredConsecutiveSuccesses, diff --git a/tools/linter/golangci-lint/.golangci.yml b/tools/linter/golangci-lint/.golangci.yml index a5cb5b7958..0a3ebd82ac 100644 --- a/tools/linter/golangci-lint/.golangci.yml +++ b/tools/linter/golangci-lint/.golangci.yml @@ -20,6 +20,8 @@ formatters: - prefix(github.com/envoyproxy/gateway) gofmt: simplify: true + gofumpt: + extra-rules: true goimports: # put imports beginning with prefix after 3rd-party packages; # it's a comma-separated list of prefixes diff --git a/tools/make/golang.mk b/tools/make/golang.mk index 62d096b157..2518df6605 100644 --- a/tools/make/golang.mk +++ b/tools/make/golang.mk @@ -103,6 +103,11 @@ go.mod.lint: go.mod.tidy go.mod.tidy.examples ## Check if go.mod is clean $(call log, "Go module looks clean!"); \ fi +.PHONY: go.lint.fmt +go.lint.fmt: + @$(LOG_TARGET) + @go tool golangci-lint fmt --build-tags=$(LINT_BUILD_TAGS) --config=tools/linter/golangci-lint/.golangci.yml + .PHONY: go.generate go.generate: ## Generate code from templates @$(LOG_TARGET) diff --git a/tools/make/lint.mk b/tools/make/lint.mk index 6d13799d63..46f746b6cf 100644 --- a/tools/make/lint.mk +++ b/tools/make/lint.mk @@ -27,7 +27,7 @@ lint: lint.yamllint lint-deps: $(tools/yamllint) lint.yamllint: $(tools/yamllint) @$(LOG_TARGET) - $(tools/yamllint) --config-file=tools/linter/yamllint/.yamllint $$(git ls-files :*.yml :*.yaml | xargs -L1 dirname | sort -u) + $(tools/yamllint) --config-file=tools/linter/yamllint/.yamllint $$(git ls-files :*.yml :*.yaml | xargs -L1 dirname | sort -u) CODESPELL_FLAGS ?= $(if $(GITHUB_ACTION),--disable-colors) .PHONY: lint.codespell From 04df840f074eb6e30a4b09abfcd25e4f95e35543 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Fri, 2 May 2025 20:07:01 +0200 Subject: [PATCH 03/66] fix: staticcheck issues (#5779) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(QF1008): Omit embedded fields from selector expression Signed-off-by: Matthieu MOREL * fix(QF1001): Apply De Morgan’s law Signed-off-by: Matthieu MOREL * fix(QF1002): Convert untagged switch to tagged switch Signed-off-by: Matthieu MOREL * fix(QF1003): Convert if/else-if chain to tagged switch Signed-off-by: Matthieu MOREL * fix(QF1007): Merge conditional assignment into variable declaration Signed-off-by: Matthieu MOREL * fix(QF1009): Use time.Time.Equal instead of == operator Signed-off-by: Matthieu MOREL --------- Signed-off-by: Matthieu MOREL Signed-off-by: Arko Dasgupta --- api/v1alpha1/envoygateway_helpers.go | 8 ++--- internal/cmd/egctl/translate_test.go | 5 ++-- internal/gatewayapi/contexts_test.go | 2 +- internal/gatewayapi/envoyextensionpolicy.go | 2 +- internal/gatewayapi/filters.go | 25 ++++++++-------- internal/gatewayapi/helpers.go | 4 +-- internal/gatewayapi/listener_test.go | 20 ++++++------- internal/gatewayapi/resource/resource_test.go | 5 ++-- internal/gatewayapi/runner/runner.go | 4 +-- internal/gatewayapi/securitypolicy.go | 4 +-- internal/gatewayapi/translator.go | 2 +- internal/gatewayapi/validate.go | 4 +-- .../infrastructure/kubernetes/infra_client.go | 4 +-- .../kubernetes/proxy/resource_provider.go | 12 ++++---- .../kubernetes/ratelimit/resource_provider.go | 4 +-- internal/infrastructure/runner/runner.go | 2 +- internal/provider/file/file.go | 2 +- internal/provider/kubernetes/controller.go | 16 +++++----- internal/provider/kubernetes/helpers.go | 2 +- internal/provider/kubernetes/routes.go | 29 ++++++++++--------- internal/provider/runner/runner.go | 4 +-- internal/wasm/cache_test.go | 2 +- internal/wasm/httpfetcher.go | 6 ++-- internal/xds/bootstrap/bootstrap.go | 5 ++-- internal/xds/translator/accesslog.go | 8 ++--- internal/xds/translator/cluster.go | 2 +- .../xds/translator/extensionserver_test.go | 5 ++-- internal/xds/translator/listener_ready.go | 6 ++-- internal/xds/translator/oidc.go | 5 +--- test/e2e/tests/backend_upgrade.go | 6 ++-- test/utils/kubernetes/kube.go | 14 ++++----- tools/linter/golangci-lint/.golangci.yml | 6 ---- 32 files changed, 110 insertions(+), 115 deletions(-) diff --git a/api/v1alpha1/envoygateway_helpers.go b/api/v1alpha1/envoygateway_helpers.go index 3efa42eca6..63b7266914 100644 --- a/api/v1alpha1/envoygateway_helpers.go +++ b/api/v1alpha1/envoygateway_helpers.go @@ -33,11 +33,11 @@ func DefaultEnvoyGateway() *EnvoyGateway { // SetEnvoyGatewayDefaults sets default EnvoyGateway configuration parameters. func (e *EnvoyGateway) SetEnvoyGatewayDefaults() { - if e.TypeMeta.Kind == "" { - e.TypeMeta.Kind = KindEnvoyGateway + if e.Kind == "" { + e.Kind = KindEnvoyGateway } - if e.TypeMeta.APIVersion == "" { - e.TypeMeta.APIVersion = GroupVersion.String() + if e.APIVersion == "" { + e.APIVersion = GroupVersion.String() } if e.Provider == nil { e.Provider = DefaultEnvoyGatewayProvider() diff --git a/internal/cmd/egctl/translate_test.go b/internal/cmd/egctl/translate_test.go index be802ef2d4..030ca31ac5 100644 --- a/internal/cmd/egctl/translate_test.go +++ b/internal/cmd/egctl/translate_test.go @@ -320,9 +320,10 @@ func TestTranslate(t *testing.T) { "testdata/translate/in/" + tc.name + ".yaml", } - if tc.output == yamlOutput { + switch tc.output { + case yamlOutput: args = append(args, "--output", yamlOutput) - } else if tc.output == jsonOutput { + case jsonOutput: args = append(args, "--output", jsonOutput) } diff --git a/internal/gatewayapi/contexts_test.go b/internal/gatewayapi/contexts_test.go index e4f45d12bb..ddaa0b8a09 100644 --- a/internal/gatewayapi/contexts_test.go +++ b/internal/gatewayapi/contexts_test.go @@ -161,7 +161,7 @@ func TestContextsStaleListener(t *testing.T) { // Ensure the listener status has been updated and the stale listener has been // removed. expectedListenerStatus := []gwapiv1.ListenerStatus{{Name: "https"}} - require.Equal(t, expectedListenerStatus, gCtx.Gateway.Status.Listeners) + require.Equal(t, expectedListenerStatus, gCtx.Status.Listeners) // Ensure that the listeners within GatewayContext have been properly updated. expectedGCtxListeners := []*ListenerContext{httpsListenerCtx} diff --git a/internal/gatewayapi/envoyextensionpolicy.go b/internal/gatewayapi/envoyextensionpolicy.go index 320d54120c..7b27adbf06 100644 --- a/internal/gatewayapi/envoyextensionpolicy.go +++ b/internal/gatewayapi/envoyextensionpolicy.go @@ -532,7 +532,7 @@ func (t *Translator) buildExtProc( NamespaceDerefOr(extProc.BackendRefs[0].Namespace, policy.Namespace)) } - traffic, err := translateTrafficFeatures(extProc.BackendCluster.BackendSettings) + traffic, err := translateTrafficFeatures(extProc.BackendSettings) if err != nil { return nil, err } diff --git a/internal/gatewayapi/filters.go b/internal/gatewayapi/filters.go index 05ec2772bf..0ca0dcd6ff 100644 --- a/internal/gatewayapi/filters.go +++ b/internal/gatewayapi/filters.go @@ -730,14 +730,14 @@ func (t *Translator) processExtensionRefHTTPFilter(extFilter *gwapiv1.LocalObjec Substitution: hrf.Spec.URLRewrite.Path.ReplaceRegexMatch.Substitution, } - if filterContext.HTTPFilterIR.URLRewrite != nil { - if filterContext.HTTPFilterIR.URLRewrite.Path == nil { - filterContext.HTTPFilterIR.URLRewrite.Path = &ir.ExtendedHTTPPathModifier{ + if filterContext.URLRewrite != nil { + if filterContext.URLRewrite.Path == nil { + filterContext.URLRewrite.Path = &ir.ExtendedHTTPPathModifier{ RegexMatchReplace: rmr, } } } else { // no url rewrite - filterContext.HTTPFilterIR.URLRewrite = &ir.URLRewrite{ + filterContext.URLRewrite = &ir.URLRewrite{ Path: &ir.ExtendedHTTPPathModifier{ RegexMatchReplace: rmr, }, @@ -748,7 +748,8 @@ func (t *Translator) processExtensionRefHTTPFilter(extFilter *gwapiv1.LocalObjec if hrf.Spec.URLRewrite.Hostname != nil { var hm *ir.HTTPHostModifier - if hrf.Spec.URLRewrite.Hostname.Type == egv1a1.HeaderHTTPHostnameModifier { + switch hrf.Spec.URLRewrite.Hostname.Type { + case egv1a1.HeaderHTTPHostnameModifier: if hrf.Spec.URLRewrite.Hostname.Header == nil { updateRouteStatusForFilter( filterContext, @@ -758,18 +759,18 @@ func (t *Translator) processExtensionRefHTTPFilter(extFilter *gwapiv1.LocalObjec hm = &ir.HTTPHostModifier{ Header: hrf.Spec.URLRewrite.Hostname.Header, } - } else if hrf.Spec.URLRewrite.Hostname.Type == egv1a1.BackendHTTPHostnameModifier { + case egv1a1.BackendHTTPHostnameModifier: hm = &ir.HTTPHostModifier{ Backend: ptr.To(true), } } - if filterContext.HTTPFilterIR.URLRewrite != nil { - if filterContext.HTTPFilterIR.URLRewrite.Host == nil { - filterContext.HTTPFilterIR.URLRewrite.Host = hm + if filterContext.URLRewrite != nil { + if filterContext.URLRewrite.Host == nil { + filterContext.URLRewrite.Host = hm } } else { // no url rewrite - filterContext.HTTPFilterIR.URLRewrite = &ir.URLRewrite{ + filterContext.URLRewrite = &ir.URLRewrite{ Host: hm, } } @@ -801,7 +802,7 @@ func (t *Translator) processExtensionRefHTTPFilter(extFilter *gwapiv1.LocalObjec filterContext.AddResponseHeaders = append(filterContext.AddResponseHeaders, newHeader) } - filterContext.HTTPFilterIR.DirectResponse = dr + filterContext.DirectResponse = dr } if hrf.Spec.CredentialInjection != nil { @@ -834,7 +835,7 @@ func (t *Translator) processExtensionRefHTTPFilter(extFilter *gwapiv1.LocalObjec Overwrite: hrf.Spec.CredentialInjection.Overwrite, Credential: secretBytes, } - filterContext.HTTPFilterIR.CredentialInjection = injection + filterContext.CredentialInjection = injection } } } diff --git a/internal/gatewayapi/helpers.go b/internal/gatewayapi/helpers.go index a99ae629d3..20d9a5663f 100644 --- a/internal/gatewayapi/helpers.go +++ b/internal/gatewayapi/helpers.go @@ -209,8 +209,8 @@ func ValidateGRPCRouteFilter(filter *gwapiv1.GRPCRouteFilter, extGKs ...schema.G filter.Type == gwapiv1.GRPCRouteFilterResponseHeaderModifier: return nil case filter.Type == gwapiv1.GRPCRouteFilterExtensionRef: - switch { - case filter.ExtensionRef == nil: + switch filter.ExtensionRef { + case nil: return errors.New("extensionRef field must be specified for an extended filter") default: for _, gk := range extGKs { diff --git a/internal/gatewayapi/listener_test.go b/internal/gatewayapi/listener_test.go index af56d06289..cfdae67f37 100644 --- a/internal/gatewayapi/listener_test.go +++ b/internal/gatewayapi/listener_test.go @@ -369,8 +369,8 @@ func TestCheckOverlappingHostnames(t *testing.T) { } for i := range tt.gateway.listeners { tt.gateway.listeners[i].listenerStatusIdx = i - tt.gateway.Gateway.Status.Listeners[i] = gwapiv1.ListenerStatus{ - Name: tt.gateway.listeners[i].Listener.Name, + tt.gateway.Status.Listeners[i] = gwapiv1.ListenerStatus{ + Name: tt.gateway.listeners[i].Name, Conditions: []metav1.Condition{}, } } @@ -406,8 +406,8 @@ func TestCheckOverlappingHostnames(t *testing.T) { } if len(tt.expected) == 0 { - if len(tt.gateway.Gateway.Status.Listeners) != 0 { - for idx, listener := range tt.gateway.Gateway.Status.Listeners { + if len(tt.gateway.Status.Listeners) != 0 { + for idx, listener := range tt.gateway.Status.Listeners { if len(listener.Conditions) != 0 { t.Errorf("expected 0 conditions for listener %d, got %d", idx, len(listener.Conditions)) } @@ -604,9 +604,9 @@ func TestCheckOverlappingCertificates(t *testing.T) { } // Initialize listener status indices - for i := range gateway.Gateway.Status.Listeners { - gateway.Gateway.Status.Listeners[i] = gwapiv1.ListenerStatus{ - Name: tt.listeners[i].Listener.Name, + for i := range gateway.Status.Listeners { + gateway.Status.Listeners[i] = gwapiv1.ListenerStatus{ + Name: tt.listeners[i].Name, Conditions: []metav1.Condition{}, } } @@ -618,7 +618,7 @@ func TestCheckOverlappingCertificates(t *testing.T) { for _, expected := range tt.expectedStatus { found := false for _, listener := range gateway.listeners { - if string(listener.Listener.Name) != expected.listenerName { + if string(listener.Name) != expected.listenerName { continue } @@ -648,7 +648,7 @@ func TestCheckOverlappingCertificates(t *testing.T) { if condition.Type == string(gwapiv1.ListenerConditionOverlappingTLSConfig) { found := false for _, expected := range tt.expectedStatus { - if string(listener.Listener.Name) == expected.listenerName && + if string(listener.Name) == expected.listenerName && condition.Status == expected.status && condition.Reason == string(expected.reason) && condition.Message == expected.message { @@ -657,7 +657,7 @@ func TestCheckOverlappingCertificates(t *testing.T) { } } if !found { - t.Errorf("Unexpected status condition found for listener %s: %+v", listener.Listener.Name, condition) + t.Errorf("Unexpected status condition found for listener %s: %+v", listener.Name, condition) } } } diff --git a/internal/gatewayapi/resource/resource_test.go b/internal/gatewayapi/resource/resource_test.go index 00e39d5db4..f5758b4319 100644 --- a/internal/gatewayapi/resource/resource_test.go +++ b/internal/gatewayapi/resource/resource_test.go @@ -177,9 +177,10 @@ func TestGetEndpointSlicesForBackendDualStack(t *testing.T) { var ipv4Slice, ipv6Slice *discoveryv1.EndpointSlice for _, slice := range result { - if slice.AddressType == discoveryv1.AddressTypeIPv4 { + switch slice.AddressType { + case discoveryv1.AddressTypeIPv4: ipv4Slice = slice - } else if slice.AddressType == discoveryv1.AddressTypeIPv6 { + case discoveryv1.AddressTypeIPv6: ipv6Slice = slice } } diff --git a/internal/gatewayapi/runner/runner.go b/internal/gatewayapi/runner/runner.go index 6febd8e581..1a8e3b00e3 100644 --- a/internal/gatewayapi/runner/runner.go +++ b/internal/gatewayapi/runner/runner.go @@ -102,7 +102,7 @@ func (r *Runner) startWasmCache(ctx context.Context) { return } cacheOption := wasm.CacheOptions{} - if r.Config.EnvoyGateway.Provider.Type == egv1a1.ProviderTypeKubernetes { + if r.EnvoyGateway.Provider.Type == egv1a1.ProviderTypeKubernetes { cacheOption.CacheDir = "/var/lib/eg/wasm" } else { h, _ := os.UserHomeDir() // Assume we always get the home directory. @@ -152,7 +152,7 @@ func (r *Runner) subscribeAndTranslate(sub <-chan watchable.Snapshot[string, *re for _, resources := range *val { // Translate and publish IRs. t := &gatewayapi.Translator{ - GatewayControllerName: r.Server.EnvoyGateway.Gateway.ControllerName, + GatewayControllerName: r.EnvoyGateway.Gateway.ControllerName, GatewayClassName: gwapiv1.ObjectName(resources.GatewayClass.Name), GlobalRateLimitEnabled: r.EnvoyGateway.RateLimit != nil, EnvoyPatchPolicyEnabled: r.EnvoyGateway.ExtensionAPIs != nil && r.EnvoyGateway.ExtensionAPIs.EnableEnvoyPatchPolicy, diff --git a/internal/gatewayapi/securitypolicy.go b/internal/gatewayapi/securitypolicy.go index dd7523cade..3bc82d8907 100644 --- a/internal/gatewayapi/securitypolicy.go +++ b/internal/gatewayapi/securitypolicy.go @@ -1218,7 +1218,7 @@ func (t *Translator) buildExtAuth( backendSettings = http.BackendSettings switch { case len(http.BackendRefs) > 0: - backendRefs = http.BackendCluster.BackendRefs + backendRefs = http.BackendRefs case http.BackendRef != nil: backendRefs = []egv1a1.BackendRef{ { @@ -1233,7 +1233,7 @@ func (t *Translator) buildExtAuth( protocol = ir.GRPC backendSettings = grpc.BackendSettings switch { - case len(grpc.BackendCluster.BackendRefs) > 0: + case len(grpc.BackendRefs) > 0: backendRefs = grpc.BackendRefs case grpc.BackendRef != nil: backendRefs = []egv1a1.BackendRef{ diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go index 5b03a95606..7666322d76 100644 --- a/internal/gatewayapi/translator.go +++ b/internal/gatewayapi/translator.go @@ -307,7 +307,7 @@ func (t *Translator) InitIRs(gateways []*GatewayContext) (map[string]*ir.Xds, ma gwInfraIR.Proxy.Name = irKey gwInfraIR.Proxy.Namespace = t.ControllerNamespace if t.GatewayNamespaceMode { - gwInfraIR.Proxy.Namespace = gateway.Gateway.Namespace + gwInfraIR.Proxy.Namespace = gateway.Namespace } // save the IR references in the map before the translation starts xdsIR[irKey] = gwXdsIR diff --git a/internal/gatewayapi/validate.go b/internal/gatewayapi/validate.go index 33bae735ab..d97ee80713 100644 --- a/internal/gatewayapi/validate.go +++ b/internal/gatewayapi/validate.go @@ -287,7 +287,7 @@ func (t *Translator) validateListenerConditions(listener *ListenerContext) (isRe } // Any condition on the listener apart from Programmed=true indicates an error. - if !(lConditions[0].Type == string(gwapiv1.ListenerConditionProgrammed) && lConditions[0].Status == metav1.ConditionTrue) { + if lConditions[0].Type != string(gwapiv1.ListenerConditionProgrammed) || lConditions[0].Status != metav1.ConditionTrue { hasProgrammedCond := false hasRefsCond := false for _, existing := range lConditions { @@ -969,7 +969,7 @@ func (t *Translator) validateExtServiceBackendReference( return errors.New("kind is invalid, only Service (specified by omitting " + "the kind field or setting it to 'Service') and Backend are supported") } - if backendRef.Port == nil && !(backendRef.Kind != nil && *backendRef.Kind == egv1a1.KindBackend) { + if backendRef.Port == nil && (backendRef.Kind == nil || *backendRef.Kind != egv1a1.KindBackend) { return errors.New("a valid port number corresponding to a port on the Service must be specified") } diff --git a/internal/infrastructure/kubernetes/infra_client.go b/internal/infrastructure/kubernetes/infra_client.go index 2cee236044..7840b6413c 100644 --- a/internal/infrastructure/kubernetes/infra_client.go +++ b/internal/infrastructure/kubernetes/infra_client.go @@ -27,7 +27,7 @@ func New(cli client.Client) *InfraClient { func (cli *InfraClient) ServerSideApply(ctx context.Context, obj client.Object) error { opts := []client.PatchOption{client.ForceOwnership, client.FieldOwner("envoy-gateway")} - if err := cli.Client.Patch(ctx, obj, client.Apply, opts...); err != nil { + if err := cli.Patch(ctx, obj, client.Apply, opts...); err != nil { return fmt.Errorf("failed to create/update resource with server-side apply for obj %v: %w", obj, err) } @@ -82,7 +82,7 @@ func (cli *InfraClient) Delete(ctx context.Context, object client.Object) error // GetUID retrieves the uid of one resource. func (cli *InfraClient) GetUID(ctx context.Context, key client.ObjectKey, current client.Object) (types.UID, error) { - if err := cli.Client.Get(ctx, key, current); err != nil { + if err := cli.Get(ctx, key, current); err != nil { return "", err } return current.GetUID(), nil diff --git a/internal/infrastructure/kubernetes/proxy/resource_provider.go b/internal/infrastructure/kubernetes/proxy/resource_provider.go index 0ec2e5a972..002e08a465 100644 --- a/internal/infrastructure/kubernetes/proxy/resource_provider.go +++ b/internal/infrastructure/kubernetes/proxy/resource_provider.go @@ -204,9 +204,9 @@ func (r *ResourceRender) Service() (*corev1.Service, error) { // set name if envoyServiceConfig.Name != nil { - svc.ObjectMeta.Name = *envoyServiceConfig.Name + svc.Name = *envoyServiceConfig.Name } else { - svc.ObjectMeta.Name = r.Name() + svc.Name = r.Name() } // apply merge patch to service @@ -338,9 +338,9 @@ func (r *ResourceRender) Deployment() (*appsv1.Deployment, error) { // set name if deploymentConfig.Name != nil { - deployment.ObjectMeta.Name = *deploymentConfig.Name + deployment.Name = *deploymentConfig.Name } else { - deployment.ObjectMeta.Name = r.Name() + deployment.Name = r.Name() } // apply merge patch to deployment @@ -408,9 +408,9 @@ func (r *ResourceRender) DaemonSet() (*appsv1.DaemonSet, error) { // set name if daemonSetConfig.Name != nil { - daemonSet.ObjectMeta.Name = *daemonSetConfig.Name + daemonSet.Name = *daemonSetConfig.Name } else { - daemonSet.ObjectMeta.Name = r.Name() + daemonSet.Name = r.Name() } // apply merge patch to daemonset diff --git a/internal/infrastructure/kubernetes/ratelimit/resource_provider.go b/internal/infrastructure/kubernetes/ratelimit/resource_provider.go index 884eedb39c..a4016ac85b 100644 --- a/internal/infrastructure/kubernetes/ratelimit/resource_provider.go +++ b/internal/infrastructure/kubernetes/ratelimit/resource_provider.go @@ -262,9 +262,9 @@ func (r *ResourceRender) Deployment() (*appsv1.Deployment, error) { // set name if r.rateLimitDeployment.Name != nil { - deployment.ObjectMeta.Name = *r.rateLimitDeployment.Name + deployment.Name = *r.rateLimitDeployment.Name } else { - deployment.ObjectMeta.Name = r.Name() + deployment.Name = r.Name() } if r.ownerReferenceUID != nil { diff --git a/internal/infrastructure/runner/runner.go b/internal/infrastructure/runner/runner.go index 4de124caf8..86c9f2d4f3 100644 --- a/internal/infrastructure/runner/runner.go +++ b/internal/infrastructure/runner/runner.go @@ -51,7 +51,7 @@ func (r *Runner) Start(ctx context.Context) (err error) { return nil } - r.mgr, err = infrastructure.NewManager(ctx, &r.Config.Server, r.Logger) + r.mgr, err = infrastructure.NewManager(ctx, &r.Server, r.Logger) if err != nil { r.Logger.Error(err, "failed to create new manager") return err diff --git a/internal/provider/file/file.go b/internal/provider/file/file.go index 1cacb8e602..15777137bb 100644 --- a/internal/provider/file/file.go +++ b/internal/provider/file/file.go @@ -51,7 +51,7 @@ func New(svr *config.Server, resources *message.ProviderResources) (*Provider, e logger: logger, watcher: filewatcher.NewWatcher(), resourcesStore: newResourcesStore(svr.EnvoyGateway.Gateway.ControllerName, resources, logger), - extensionManagerEnabled: svr.EnvoyGateway.EnvoyGatewaySpec.ExtensionManager != nil, + extensionManagerEnabled: svr.EnvoyGateway.ExtensionManager != nil, }, nil } diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go index e84963337f..0e613e80f1 100644 --- a/internal/provider/kubernetes/controller.go +++ b/internal/provider/kubernetes/controller.go @@ -153,11 +153,11 @@ func newGatewayAPIController(ctx context.Context, mgr manager.Manager, cfg *conf case <-ctx.Done(): return case <-cfg.Elected: - r.subscribeAndUpdateStatus(ctx, cfg.EnvoyGateway.EnvoyGatewaySpec.ExtensionManager != nil) + r.subscribeAndUpdateStatus(ctx, cfg.EnvoyGateway.ExtensionManager != nil) } }() } else { - r.subscribeAndUpdateStatus(ctx, cfg.EnvoyGateway.EnvoyGatewaySpec.ExtensionManager != nil) + r.subscribeAndUpdateStatus(ctx, cfg.EnvoyGateway.ExtensionManager != nil) } return nil } @@ -1857,9 +1857,9 @@ func (r *gatewayAPIReconciler) processGatewayParamsRef(ctx context.Context, gtw } ref := gtw.Spec.Infrastructure.ParametersRef - if !(string(ref.Group) == egv1a1.GroupVersion.Group && - ref.Kind == egv1a1.KindEnvoyProxy && - len(ref.Name) > 0) { + if string(ref.Group) != egv1a1.GroupVersion.Group || + ref.Kind != egv1a1.KindEnvoyProxy || + len(ref.Name) == 0 { return fmt.Errorf("unsupported parametersRef for gateway %s/%s", gtw.Namespace, gtw.Name) } @@ -1964,8 +1964,8 @@ func (r *gatewayAPIReconciler) processEnvoyProxy(ep *egv1a1.EnvoyProxy, resource for _, backendRef := range backendRefs { backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, ep.Namespace) resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.BackendObjectReference.Group, - Kind: backendRef.BackendObjectReference.Kind, + Group: backendRef.Group, + Kind: backendRef.Kind, Namespace: gatewayapi.NamespacePtr(backendNamespace), Name: backendRef.Name, }) @@ -2104,7 +2104,7 @@ func (r *gatewayAPIReconciler) processExtensionServerPolicies( } _, foundTargetRef := policySpec["targetRef"] _, foundTargetRefs := policySpec["targetRefs"] - if !(foundTargetRef || foundTargetRefs) { + if !foundTargetRef && !foundTargetRefs { return fmt.Errorf("not a policy object - no targetRef or targetRefs found in %s.%s %s", policy.GetAPIVersion(), policy.GetKind(), policy.GetName()) } diff --git a/internal/provider/kubernetes/helpers.go b/internal/provider/kubernetes/helpers.go index 0069aba5c3..634f64aef9 100644 --- a/internal/provider/kubernetes/helpers.go +++ b/internal/provider/kubernetes/helpers.go @@ -158,7 +158,7 @@ func validateBackendRef(ref *gwapiv1.BackendRef) error { return fmt.Errorf("invalid group; must be nil, empty string %q or %q", mcsapiv1a1.GroupName, egv1a1.GroupName) case gatewayapi.KindDerefOr(ref.Kind, resource.KindService) != resource.KindService && gatewayapi.KindDerefOr(ref.Kind, resource.KindService) != resource.KindServiceImport && gatewayapi.KindDerefOr(ref.Kind, resource.KindService) != egv1a1.KindBackend: return fmt.Errorf("invalid kind %q; must be %q, %q or %q", - *ref.BackendObjectReference.Kind, resource.KindService, resource.KindServiceImport, egv1a1.KindBackend) + *ref.Kind, resource.KindService, resource.KindServiceImport, egv1a1.KindBackend) } return nil diff --git a/internal/provider/kubernetes/routes.go b/internal/provider/kubernetes/routes.go index ddb0339120..f2c676066f 100644 --- a/internal/provider/kubernetes/routes.go +++ b/internal/provider/kubernetes/routes.go @@ -64,8 +64,8 @@ func (r *gatewayAPIReconciler) processTLSRoutes(ctx context.Context, gatewayName backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, tlsRoute.Namespace) resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.BackendObjectReference.Group, - Kind: backendRef.BackendObjectReference.Kind, + Group: backendRef.Group, + Kind: backendRef.Kind, Namespace: gatewayapi.NamespacePtr(backendNamespace), Name: backendRef.Name, }) @@ -146,8 +146,8 @@ func (r *gatewayAPIReconciler) processGRPCRoutes(ctx context.Context, gatewayNam backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, grpcRoute.Namespace) resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.BackendObjectReference.Group, - Kind: backendRef.BackendObjectReference.Kind, + Group: backendRef.Group, + Kind: backendRef.Kind, Namespace: gatewayapi.NamespacePtr(backendNamespace), Name: backendRef.Name, }) @@ -284,8 +284,8 @@ func (r *gatewayAPIReconciler) processHTTPRoutes(ctx context.Context, gatewayNam backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, httpRoute.Namespace) resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.BackendObjectReference.Group, - Kind: backendRef.BackendObjectReference.Kind, + Group: backendRef.Group, + Kind: backendRef.Kind, Namespace: gatewayapi.NamespacePtr(backendNamespace), Name: backendRef.Name, }) @@ -363,7 +363,8 @@ func (r *gatewayAPIReconciler) processHTTPRouteFilter( } // Load in the backendRefs from any requestMirrorFilters on the HTTPRoute - if filter.Type == gwapiv1.HTTPRouteFilterRequestMirror { + switch filter.Type { + case gwapiv1.HTTPRouteFilterRequestMirror: // Make sure the config actually exists mirrorFilter := filter.RequestMirror if mirrorFilter == nil { @@ -384,8 +385,8 @@ func (r *gatewayAPIReconciler) processHTTPRouteFilter( backendNamespace := gatewayapi.NamespaceDerefOr(mirrorBackendRef.Namespace, httpRoute.Namespace) resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: mirrorBackendRef.BackendObjectReference.Group, - Kind: mirrorBackendRef.BackendObjectReference.Kind, + Group: mirrorBackendRef.Group, + Kind: mirrorBackendRef.Kind, Namespace: gatewayapi.NamespacePtr(backendNamespace), Name: mirrorBackendRef.Name, }) @@ -418,7 +419,7 @@ func (r *gatewayAPIReconciler) processHTTPRouteFilter( } } } - } else if filter.Type == gwapiv1.HTTPRouteFilterExtensionRef { + case gwapiv1.HTTPRouteFilterExtensionRef: // NOTE: filters must be in the same namespace as the HTTPRoute // Check if it's a Kind managed by an extension and add to resourceTree key := utils.NamespacedNameWithGroupKind{ @@ -502,8 +503,8 @@ func (r *gatewayAPIReconciler) processTCPRoutes(ctx context.Context, gatewayName backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, tcpRoute.Namespace) resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.BackendObjectReference.Group, - Kind: backendRef.BackendObjectReference.Kind, + Group: backendRef.Group, + Kind: backendRef.Kind, Namespace: gatewayapi.NamespacePtr(backendNamespace), Name: backendRef.Name, }) @@ -583,8 +584,8 @@ func (r *gatewayAPIReconciler) processUDPRoutes(ctx context.Context, gatewayName backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, udpRoute.Namespace) resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.BackendObjectReference.Group, - Kind: backendRef.BackendObjectReference.Kind, + Group: backendRef.Group, + Kind: backendRef.Kind, Namespace: gatewayapi.NamespacePtr(backendNamespace), Name: backendRef.Name, }) diff --git a/internal/provider/runner/runner.go b/internal/provider/runner/runner.go index 673ea867d4..a475e3204a 100644 --- a/internal/provider/runner/runner.go +++ b/internal/provider/runner/runner.go @@ -79,7 +79,7 @@ func (r *Runner) createKubernetesProvider(ctx context.Context) (*kubernetes.Prov return nil, fmt.Errorf("failed to get kubeconfig: %w", err) } - p, err := kubernetes.New(ctx, cfg, &r.Config.Server, r.ProviderResources) + p, err := kubernetes.New(ctx, cfg, &r.Server, r.ProviderResources) if err != nil { return nil, fmt.Errorf("failed to create provider %s: %w", egv1a1.ProviderTypeKubernetes, err) } @@ -90,7 +90,7 @@ func (r *Runner) createKubernetesProvider(ctx context.Context) (*kubernetes.Prov func (r *Runner) createCustomResourceProvider() (p provider.Provider, err error) { switch r.EnvoyGateway.Provider.Custom.Resource.Type { case egv1a1.ResourceProviderTypeFile: - p, err = file.New(&r.Config.Server, r.ProviderResources) + p, err = file.New(&r.Server, r.ProviderResources) if err != nil { return nil, fmt.Errorf("failed to create provider %s: %w", egv1a1.ProviderTypeCustom, err) } diff --git a/internal/wasm/cache_test.go b/internal/wasm/cache_test.go index c23b0adc65..55238df35c 100644 --- a/internal/wasm/cache_test.go +++ b/internal/wasm/cache_test.go @@ -763,7 +763,7 @@ func TestWasmCache(t *testing.T) { cache.mux.Lock() if cacheHitKey != nil { - if entry, ok := cache.modules[*cacheHitKey]; ok && entry.last == initTime { + if entry, ok := cache.modules[*cacheHitKey]; ok && initTime.Equal(entry.last) { t.Errorf("Wasm module cache entry's last access time not updated after get operation, key: %v", *cacheHitKey) } } diff --git a/internal/wasm/httpfetcher.go b/internal/wasm/httpfetcher.go index 6850ef9974..41325addfd 100644 --- a/internal/wasm/httpfetcher.go +++ b/internal/wasm/httpfetcher.go @@ -152,9 +152,9 @@ func (f *HTTPFetcher) Fetch(ctx context.Context, url string, allowInsecure bool) func retryable(code int) bool { return code >= 500 && - !(code == http.StatusNotImplemented || - code == http.StatusHTTPVersionNotSupported || - code == http.StatusNetworkAuthenticationRequired) + (code != http.StatusNotImplemented && + code != http.StatusHTTPVersionNotSupported && + code != http.StatusNetworkAuthenticationRequired) } func isPosixTar(b []byte) bool { diff --git a/internal/xds/bootstrap/bootstrap.go b/internal/xds/bootstrap/bootstrap.go index 8b3644a9d5..04d847ee76 100644 --- a/internal/xds/bootstrap/bootstrap.go +++ b/internal/xds/bootstrap/bootstrap.go @@ -301,10 +301,11 @@ func GetRenderedBootstrapConfig(opts *RenderBootstrapConfigOptions) (string, err if opts.IPFamily != nil { cfg.parameters.IPFamily = string(*opts.IPFamily) - if *opts.IPFamily == egv1a1.IPv6 { + switch *opts.IPFamily { + case egv1a1.IPv6: cfg.parameters.AdminServer.Address = EnvoyAdminAddressV6 cfg.parameters.StatsServer.Address = netutils.IPv6ListenerAddress - } else if *opts.IPFamily == egv1a1.DualStack { + case egv1a1.DualStack: cfg.parameters.StatsServer.Address = netutils.IPv6ListenerAddress } } diff --git a/internal/xds/translator/accesslog.go b/internal/xds/translator/accesslog.go index 304f22da49..e707c24dac 100644 --- a/internal/xds/translator/accesslog.go +++ b/internal/xds/translator/accesslog.go @@ -129,7 +129,7 @@ func buildXdsAccessLog(al *ir.AccessLog, accessLogType ir.ProxyAccessLogType) ([ // handle text file access logs for _, text := range al.Text { // Filter out logs that are not Global or match the desired access log type - if !(text.LogType == nil || *text.LogType == accessLogType) { + if text.LogType != nil && *text.LogType != accessLogType { continue } @@ -182,7 +182,7 @@ func buildXdsAccessLog(al *ir.AccessLog, accessLogType ir.ProxyAccessLogType) ([ // handle json file access logs for _, json := range al.JSON { // Filter out logs that are not Global or match the desired access log type - if !(json.LogType == nil || *json.LogType == accessLogType) { + if json.LogType != nil && *json.LogType != accessLogType { continue } @@ -242,7 +242,7 @@ func buildXdsAccessLog(al *ir.AccessLog, accessLogType ir.ProxyAccessLogType) ([ // handle ALS access logs for _, als := range al.ALS { // Filter out logs that are not Global or match the desired access log type - if !(als.LogType == nil || *als.LogType == accessLogType) { + if als.LogType != nil && *als.LogType != accessLogType { continue } @@ -313,7 +313,7 @@ func buildXdsAccessLog(al *ir.AccessLog, accessLogType ir.ProxyAccessLogType) ([ // handle open telemetry access logs for _, otel := range al.OpenTelemetry { // Filter out logs that are not Global or match the desired access log type - if !(otel.LogType == nil || *otel.LogType == accessLogType) { + if otel.LogType != nil && *otel.LogType != accessLogType { continue } diff --git a/internal/xds/translator/cluster.go b/internal/xds/translator/cluster.go index 92c726e1ef..0d240527e4 100644 --- a/internal/xds/translator/cluster.go +++ b/internal/xds/translator/cluster.go @@ -752,7 +752,7 @@ func buildTypedExtensionProtocolOptions(args *xdsClusterArgs) (map[string]*anypb requiresHTTPFilters := len(args.settings) > 0 && args.settings[0].Filters != nil && args.settings[0].Filters.CredentialInjection != nil - if !(requiresCommonHTTPOptions || requiresHTTP1Options || requiresHTTP2Options || args.useClientProtocol || requiresHTTPFilters) { + if !requiresCommonHTTPOptions && !requiresHTTP1Options && !requiresHTTP2Options && !args.useClientProtocol && !requiresHTTPFilters { return nil, nil, nil } diff --git a/internal/xds/translator/extensionserver_test.go b/internal/xds/translator/extensionserver_test.go index 89a009bfcb..0579b0ca33 100644 --- a/internal/xds/translator/extensionserver_test.go +++ b/internal/xds/translator/extensionserver_test.go @@ -95,11 +95,12 @@ func (t *testingExtensionServer) PostRouteModify(_ context.Context, req *pb.Post func (t *testingExtensionServer) PostVirtualHostModify(_ context.Context, req *pb.PostVirtualHostModifyRequest) (*pb.PostVirtualHostModifyResponse, error) { // Only make the change when the VirtualHost's name matches the expected testdata // This prevents us from having to update every single testfile.out - if req.VirtualHost.Name == "extension-post-xdsvirtualhost-hook-error/*" { + switch req.VirtualHost.Name { + case "extension-post-xdsvirtualhost-hook-error/*": return &pb.PostVirtualHostModifyResponse{ VirtualHost: req.VirtualHost, }, fmt.Errorf("extension post xds virtual host hook error") - } else if req.VirtualHost.Name == "extension-listener" { + case "extension-listener": // 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(req.VirtualHost).(*routeV3.VirtualHost) diff --git a/internal/xds/translator/listener_ready.go b/internal/xds/translator/listener_ready.go index 7a60b0f425..1b4ebda769 100644 --- a/internal/xds/translator/listener_ready.go +++ b/internal/xds/translator/listener_ready.go @@ -22,10 +22,8 @@ import ( ) func buildReadyListener(ready *ir.ReadyListener) (*listenerv3.Listener, error) { - ipv4Compact := false - if ready.IPFamily == egv1a1.IPv6 || ready.IPFamily == egv1a1.DualStack { - ipv4Compact = true - } + ipv4Compact := ready.IPFamily == egv1a1.IPv6 || ready.IPFamily == egv1a1.DualStack + hcmFilters := make([]*hcmv3.HttpFilter, 0, 3) healthcheckFilter, err := filters.GenerateHealthCheckFilter(bootstrap.EnvoyReadinessPath) if err != nil { diff --git a/internal/xds/translator/oidc.go b/internal/xds/translator/oidc.go index fc1814f2da..60527be071 100644 --- a/internal/xds/translator/oidc.go +++ b/internal/xds/translator/oidc.go @@ -119,13 +119,10 @@ func oauth2Config(oidc *ir.OIDC) (*oauth2v3.OAuth2, error) { } // Envoy OAuth2 filter deletes the HTTP authorization header by default, which surprises users. - preserveAuthorizationHeader := true // If the user wants to forward the oauth2 access token to the upstream service, // we should not preserve the original authorization header. - if oidc.ForwardAccessToken { - preserveAuthorizationHeader = false - } + preserveAuthorizationHeader := !oidc.ForwardAccessToken oauth2 := &oauth2v3.OAuth2{ Config: &oauth2v3.OAuth2Config{ diff --git a/test/e2e/tests/backend_upgrade.go b/test/e2e/tests/backend_upgrade.go index e7bbf897c4..20f03a8cc7 100644 --- a/test/e2e/tests/backend_upgrade.go +++ b/test/e2e/tests/backend_upgrade.go @@ -90,11 +90,11 @@ func restartDeploymentAndWaitForNewPods(t *testing.T, timeoutConfig config.Timeo ctx := context.Background() - if dp.Spec.Template.ObjectMeta.Annotations == nil { - dp.Spec.Template.ObjectMeta.Annotations = make(map[string]string) + if dp.Spec.Template.Annotations == nil { + dp.Spec.Template.Annotations = make(map[string]string) } restartTime := time.Now().Format(time.RFC3339) - dp.Spec.Template.ObjectMeta.Annotations[kubeRestartAnnotation] = restartTime + dp.Spec.Template.Annotations[kubeRestartAnnotation] = restartTime if err := c.Update(ctx, dp); err != nil { return err diff --git a/test/utils/kubernetes/kube.go b/test/utils/kubernetes/kube.go index d3217fbad9..d2d031c3b5 100644 --- a/test/utils/kubernetes/kube.go +++ b/test/utils/kubernetes/kube.go @@ -80,7 +80,7 @@ func (ka *KubeActions) ManageEgress(ctx context.Context, ip, namespace, policyNa // remove the policy if !blockTraffic { - if err := ka.Client.Delete(ctx, netPolicy); err != nil { + if err := ka.Delete(ctx, netPolicy); err != nil { return nil, fmt.Errorf("failed to delete NetworkPolicy: %w", err) } return nil, nil @@ -88,14 +88,14 @@ func (ka *KubeActions) ManageEgress(ctx context.Context, ip, namespace, policyNa if kerrors.IsNotFound(err) { // Create the NetworkPolicy if it doesn't exist - if err := ka.Client.Create(ctx, netPolicy); err != nil { + if err := ka.Create(ctx, netPolicy); err != nil { return nil, fmt.Errorf("failed to create NetworkPolicy: %w", err) } fmt.Printf("NetworkPolicy %s created.\n", netPolicy.Name) } else { // Update the existing NetworkPolicy existingPolicy.Spec = netPolicy.Spec - if err := ka.Client.Update(ctx, existingPolicy); err != nil { + if err := ka.Update(ctx, existingPolicy); err != nil { return nil, fmt.Errorf("failed to update NetworkPolicy: %w", err) } fmt.Printf("NetworkPolicy %s updated.\n", netPolicy.Name) @@ -114,7 +114,7 @@ func (ka *KubeActions) ScaleDeploymentAndWait(ctx context.Context, deploymentNam return err } } else { - err := ka.Client.Get(ctx, client.ObjectKey{Name: deploymentName, Namespace: namespace}, deployment) + err := ka.Get(ctx, client.ObjectKey{Name: deploymentName, Namespace: namespace}, deployment) if err != nil { return err } @@ -124,7 +124,7 @@ func (ka *KubeActions) ScaleDeploymentAndWait(ctx context.Context, deploymentNam deployment.Spec.Replicas = &replicas // Apply the update - err := ka.Client.Update(ctx, deployment) + err := ka.Update(ctx, deployment) if err != nil { return err } @@ -138,7 +138,7 @@ func (ka *KubeActions) ScaleEnvoyProxy(envoyProxyName, namespace string, replica // Retrieve the existing EnvoyProxy resource envoyProxy := &egv1a1.EnvoyProxy{} - err := ka.Client.Get(ctx, types.NamespacedName{Name: envoyProxyName, Namespace: namespace}, envoyProxy) + err := ka.Get(ctx, types.NamespacedName{Name: envoyProxyName, Namespace: namespace}, envoyProxy) if err != nil { return fmt.Errorf("failed to get EnvoyProxy: %w", err) } @@ -152,7 +152,7 @@ func (ka *KubeActions) ScaleEnvoyProxy(envoyProxyName, namespace string, replica envoyProxy.Spec.Provider.Kubernetes.EnvoyDeployment.Replicas = &replicas // Apply the update - err = ka.Client.Update(ctx, envoyProxy) + err = ka.Update(ctx, envoyProxy) if err != nil { return fmt.Errorf("failed to update EnvoyProxy: %w", err) } diff --git a/tools/linter/golangci-lint/.golangci.yml b/tools/linter/golangci-lint/.golangci.yml index 0a3ebd82ac..96c46d642e 100644 --- a/tools/linter/golangci-lint/.golangci.yml +++ b/tools/linter/golangci-lint/.golangci.yml @@ -134,13 +134,7 @@ linters: staticcheck: checks: - all - - -QF1001 - - -QF1002 - - -QF1003 - -QF1006 - - -QF1007 - - -QF1008 - - -QF1009 - -ST1005 testifylint: disable: From 3af260ca2c7abc3da73b3c6bde0030c91d9482d9 Mon Sep 17 00:00:00 2001 From: "Huabing (Robin) Zhao" Date: Sat, 3 May 2025 03:06:53 +0800 Subject: [PATCH 04/66] docs: local jwks (#5806) docs for local jwks Signed-off-by: Huabing (Robin) Zhao Signed-off-by: Arko Dasgupta --- .../tasks/security/jwt-authentication.md | 112 +++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/site/content/en/latest/tasks/security/jwt-authentication.md b/site/content/en/latest/tasks/security/jwt-authentication.md index b90e17ad73..0f904a3409 100644 --- a/site/content/en/latest/tasks/security/jwt-authentication.md +++ b/site/content/en/latest/tasks/security/jwt-authentication.md @@ -150,10 +150,13 @@ You should see the below response ## Connect to a remote JWKS with Self-Signed Certificate -To connect to a remote JWKS with a self-signed certificate, you need to configure it using the [Backend] resource within the [SecurityPolicy]. Additionally, use the [BackendTLSPolicy] to specify the CA certificate required to authenticate the JWKS host. +To connect to a remote JWKS with a self-signed certificate, you need to configure it using the [BackendTLSPolicy] to specify the CA certificate required to authenticate the JWKS host. Additionally, if the JWKS is outside of the cluster, you need to configure the [Backend] resource to specify the JWKS host. The following example demonstrates how to configure the remote JWKS with a self-signed certificate. +Please note that the `Backend` resource is not required if the JWKS is hosted on the same cluster as the Envoy Gateway, since +it can be accessed directly via the Kubernetes Service DNS name. + {{< tabpane text=true >}} {{% tab header="Apply from stdin" %}} @@ -289,6 +292,113 @@ Additional connection settings for the remote JWKS host can be configured in the For more information about [Backend] and [BackendTLSPolicy], refer to the [Backend Routing][backend-routing] and [Backend TLS: Gateway to Backend][backend-tls] tasks. +## Use a local JWKS to authenticate requests + +Envoy Gateway also supports using a local JWKS stored in a Kubernetes ConfigMap to authenticate requests. + +The following example demonstrates how to configure a local JWKS within the [SecurityPolicy] resource. + +{{< tabpane text=true >}} +{{% tab header="Apply from stdin" %}} + +```shell +cat <}} + +With the above configuration, the [SecurityPolicy] resource will use the JWKS stored in the `jwt-local-jwks` ConfigMap to authenticate requests for the `foo` HTTPRoute. + ## Clean-Up Follow the steps from the [Quickstart](../../quickstart) to uninstall Envoy Gateway and the example manifest. From 76ead72ca931160b322c50db726b6e2aa50ce946 Mon Sep 17 00:00:00 2001 From: Arko Dasgupta Date: Fri, 2 May 2025 12:09:11 -0700 Subject: [PATCH 05/66] disable settings by default in gateway-crds-helm (#5894) * disable settings by default in gateway-crds-helm * These settings dont work by default in the way `helm` works and this helm chart is now mainly used as a package artifact to be consumed by CI tools like Argo, so changed the default settings to disable by default, so users are opting into specific CRDs they want. Relates to https://github.com/envoyproxy/gateway/pull/5616#issuecomment-2840245509 Signed-off-by: Arko Dasgupta --- charts/gateway-crds-helm/README.md | 4 +- charts/gateway-crds-helm/values.tmpl.yaml | 4 +- charts/gateway-crds-helm/values.yaml | 4 +- .../latest/install/gateway-crds-helm-api.md | 4 +- test/helm/gateway-crds-helm/all.in.yaml | 5 + test/helm/gateway-crds-helm/all.out.yaml | 43461 ++++++++++++++++ test/helm/gateway-crds-helm/default.in.yaml | 5 + test/helm/gateway-crds-helm/default.out.yaml | 1 + .../envoy-gateway-crds.in.yaml | 5 + .../envoy-gateway-crds.out.yaml | 26130 ++++++++++ .../gateway-api-crds.in.yaml | 5 + .../gateway-api-crds.out.yaml | 17331 ++++++ tools/linter/codespell/.codespell.skip | 1 + tools/linter/yamllint/.yamllint | 6 +- tools/make/helm.mk | 8 +- 15 files changed, 86961 insertions(+), 13 deletions(-) create mode 100644 test/helm/gateway-crds-helm/all.in.yaml create mode 100644 test/helm/gateway-crds-helm/all.out.yaml create mode 100644 test/helm/gateway-crds-helm/default.in.yaml create mode 100644 test/helm/gateway-crds-helm/default.out.yaml create mode 100644 test/helm/gateway-crds-helm/envoy-gateway-crds.in.yaml create mode 100644 test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml create mode 100644 test/helm/gateway-crds-helm/gateway-api-crds.in.yaml create mode 100644 test/helm/gateway-crds-helm/gateway-api-crds.out.yaml diff --git a/charts/gateway-crds-helm/README.md b/charts/gateway-crds-helm/README.md index 6a31f8d358..87d5388c74 100644 --- a/charts/gateway-crds-helm/README.md +++ b/charts/gateway-crds-helm/README.md @@ -32,6 +32,6 @@ To uninstall the chart: | Key | Type | Default | Description | |-----|------|---------|-------------| -| crds.envoyGateway.enabled | bool | `true` | | -| crds.gatewayAPI.enabled | bool | `true` | | +| crds.envoyGateway.enabled | bool | `false` | | +| crds.gatewayAPI.enabled | bool | `false` | | diff --git a/charts/gateway-crds-helm/values.tmpl.yaml b/charts/gateway-crds-helm/values.tmpl.yaml index 80cebdc4d3..d1433de319 100644 --- a/charts/gateway-crds-helm/values.tmpl.yaml +++ b/charts/gateway-crds-helm/values.tmpl.yaml @@ -1,5 +1,5 @@ crds: gatewayAPI: - enabled: true + enabled: false envoyGateway: - enabled: true + enabled: false diff --git a/charts/gateway-crds-helm/values.yaml b/charts/gateway-crds-helm/values.yaml index 80cebdc4d3..d1433de319 100644 --- a/charts/gateway-crds-helm/values.yaml +++ b/charts/gateway-crds-helm/values.yaml @@ -1,5 +1,5 @@ crds: gatewayAPI: - enabled: true + enabled: false envoyGateway: - enabled: true + enabled: false diff --git a/site/content/en/latest/install/gateway-crds-helm-api.md b/site/content/en/latest/install/gateway-crds-helm-api.md index 3b92824b23..c2f3d84882 100644 --- a/site/content/en/latest/install/gateway-crds-helm-api.md +++ b/site/content/en/latest/install/gateway-crds-helm-api.md @@ -10,6 +10,6 @@ A Helm chart for Kubernetes | Key | Type | Default | Description | |-----|------|---------|-------------| -| crds.envoyGateway.enabled | bool | `true` | | -| crds.gatewayAPI.enabled | bool | `true` | | +| crds.envoyGateway.enabled | bool | `false` | | +| crds.gatewayAPI.enabled | bool | `false` | | diff --git a/test/helm/gateway-crds-helm/all.in.yaml b/test/helm/gateway-crds-helm/all.in.yaml new file mode 100644 index 0000000000..25b2674a31 --- /dev/null +++ b/test/helm/gateway-crds-helm/all.in.yaml @@ -0,0 +1,5 @@ +crds: + gatewayAPI: + enabled: true + envoyGateway: + enabled: true diff --git a/test/helm/gateway-crds-helm/all.out.yaml b/test/helm/gateway-crds-helm/all.out.yaml new file mode 100644 index 0000000000..68bac7a103 --- /dev/null +++ b/test/helm/gateway-crds-helm/all.out.yaml @@ -0,0 +1,43461 @@ +--- +# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_backendtlspolicies.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + labels: + gateway.networking.k8s.io/policy: Direct + name: backendtlspolicies.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: BackendTLSPolicy + listKind: BackendTLSPolicyList + plural: backendtlspolicies + shortNames: + - btlspolicy + singular: backendtlspolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha3 + schema: + openAPIV3Schema: + description: |- + BackendTLSPolicy provides a way to configure how a Gateway + connects to a Backend via TLS. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of BackendTLSPolicy. + properties: + options: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Options are a list of key/value pairs to enable extended TLS + configuration for each implementation. For example, configuring the + minimum TLS version or supported cipher suites. + + A set of common keys MAY be defined by the API in the future. To avoid + any ambiguity, implementation-specific definitions MUST use + domain-prefixed names, such as `example.com/my-custom-option`. + Un-prefixed names are reserved for key names defined by Gateway API. + + Support: Implementation-specific + maxProperties: 16 + type: object + targetRefs: + description: |- + TargetRefs identifies an API object to apply the policy to. + Only Services have Extended support. Implementations MAY support + additional objects, with Implementation Specific support. + Note that this config applies to the entire referenced resource + by default, but this default may change in the future to provide + a more granular application of the policy. + + TargetRefs must be _distinct_. This means either that: + + * They select different targets. If this is the case, then targetRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, and `name` must + be unique across all targetRef entries in the BackendTLSPolicy. + * They select different sectionNames in the same target. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + items: + description: |- + LocalPolicyTargetReferenceWithSectionName identifies an API object to apply a + direct policy to. This should be used as part of Policy resources that can + target single resources. For more information on how this policy attachment + mode works, and a sample Policy resource, refer to the policy attachment + documentation for Gateway API. + + Note: This should only be used for direct policy attachment when references + to SectionName are actually needed. In all other cases, + LocalPolicyTargetReference should be used. + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-validations: + - message: sectionName must be specified when targetRefs includes + 2 or more references to the same target + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name ? ((!has(p1.sectionName) || p1.sectionName + == '''') == (!has(p2.sectionName) || p2.sectionName == '''')) + : true))' + - message: sectionName must be unique when targetRefs includes 2 or + more references to the same target + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.sectionName) || + p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || (has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)))) + validation: + description: Validation contains backend TLS validation configuration. + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to Kubernetes objects that + contain a PEM-encoded TLS CA certificate bundle, which is used to + validate a TLS handshake between the Gateway and backend Pod. + + If CACertificateRefs is empty or unspecified, then WellKnownCACertificates must be + specified. Only one of CACertificateRefs or WellKnownCACertificates may be specified, + not both. If CACertificateRefs is empty or unspecified, the configuration for + WellKnownCACertificates MUST be honored instead if supported by the implementation. + + References to a resource in a different namespace are invalid for the + moment, although we will revisit this in the future. + + A single CACertificateRef to a Kubernetes ConfigMap kind has "Core" support. + Implementations MAY choose to support attaching multiple certificates to + a backend, but this behavior is implementation-specific. + + Support: Core - An optional single reference to a Kubernetes ConfigMap, + with the CA certificate in a key named `ca.crt`. + + Support: Implementation-specific (More than one reference, or other kinds + of resources). + items: + description: |- + LocalObjectReference identifies an API object within the namespace of the + referrer. + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" + or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + maxItems: 8 + type: array + hostname: + description: |- + Hostname is used for two purposes in the connection between Gateways and + backends: + + 1. Hostname MUST be used as the SNI to connect to the backend (RFC 6066). + 2. Hostname MUST be used for authentication and MUST match the certificate served by the matching backend, unless SubjectAltNames is specified. + authentication and MUST match the certificate served by the matching + backend. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + subjectAltNames: + description: |- + SubjectAltNames contains one or more Subject Alternative Names. + When specified the certificate served from the backend MUST + have at least one Subject Alternate Name matching one of the specified SubjectAltNames. + + Support: Extended + items: + description: SubjectAltName represents Subject Alternative Name. + properties: + hostname: + description: |- + Hostname contains Subject Alternative Name specified in DNS name format. + Required when Type is set to Hostname, ignored otherwise. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: + description: |- + Type determines the format of the Subject Alternative Name. Always required. + + Support: Core + enum: + - Hostname + - URI + type: string + uri: + description: |- + URI contains Subject Alternative Name specified in a full URI format. + It MUST include both a scheme (e.g., "http" or "ftp") and a scheme-specific-part. + Common values include SPIFFE IDs like "spiffe://mycluster.example.com/ns/myns/sa/svc1sa". + Required when Type is set to URI, ignored otherwise. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))? + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: SubjectAltName element must contain Hostname, if + Type is set to Hostname + rule: '!(self.type == "Hostname" && (!has(self.hostname) || + self.hostname == ""))' + - message: SubjectAltName element must not contain Hostname, + if Type is not set to Hostname + rule: '!(self.type != "Hostname" && has(self.hostname) && + self.hostname != "")' + - message: SubjectAltName element must contain URI, if Type + is set to URI + rule: '!(self.type == "URI" && (!has(self.uri) || self.uri + == ""))' + - message: SubjectAltName element must not contain URI, if Type + is not set to URI + rule: '!(self.type != "URI" && has(self.uri) && self.uri != + "")' + maxItems: 5 + type: array + wellKnownCACertificates: + description: |- + WellKnownCACertificates specifies whether system CA certificates may be used in + the TLS handshake between the gateway and backend pod. + + If WellKnownCACertificates is unspecified or empty (""), then CACertificateRefs + must be specified with at least one entry for a valid configuration. Only one of + CACertificateRefs or WellKnownCACertificates may be specified, not both. If an + implementation does not support the WellKnownCACertificates field or the value + supplied is not supported, the Status Conditions on the Policy MUST be + updated to include an Accepted: False Condition with Reason: Invalid. + + Support: Implementation-specific + enum: + - System + type: string + required: + - hostname + type: object + x-kubernetes-validations: + - message: must not contain both CACertificateRefs and WellKnownCACertificates + rule: '!(has(self.caCertificateRefs) && size(self.caCertificateRefs) + > 0 && has(self.wellKnownCACertificates) && self.wellKnownCACertificates + != "")' + - message: must specify either CACertificateRefs or WellKnownCACertificates + rule: (has(self.caCertificateRefs) && size(self.caCertificateRefs) + > 0 || has(self.wellKnownCACertificates) && self.wellKnownCACertificates + != "") + required: + - targetRefs + - validation + type: object + status: + description: Status defines the current state of BackendTLSPolicy. + properties: + ancestors: + description: |- + Ancestors is a list of ancestor resources (usually Gateways) that are + associated with the policy, and the status of the policy with respect to + each ancestor. When this policy attaches to a parent, the controller that + manages the parent and the ancestors MUST add an entry to this list when + the controller first sees the policy and SHOULD update the entry as + appropriate when the relevant ancestor is modified. + + Note that choosing the relevant ancestor is left to the Policy designers; + an important part of Policy design is designing the right object level at + which to namespace this status. + + Note also that implementations MUST ONLY populate ancestor status for + the Ancestor resources they are responsible for. Implementations MUST + use the ControllerName field to uniquely identify the entries in this list + that they are responsible for. + + Note that to achieve this, the list of PolicyAncestorStatus structs + MUST be treated as a map with a composite key, made up of the AncestorRef + and ControllerName fields combined. + + A maximum of 16 ancestors will be represented in this list. An empty list + means the Policy is not relevant for any ancestors. + + If this slice is full, implementations MUST NOT add further entries. + Instead they MUST consider the policy unimplementable and signal that + on any related resources such as the ancestor that would be referenced + here. For example, if this list was full on BackendTLSPolicy, no + additional Gateways would be able to reference the Service targeted by + the BackendTLSPolicy. + items: + description: |- + PolicyAncestorStatus describes the status of a route with respect to an + associated Ancestor. + + Ancestors refer to objects that are either the Target of a policy or above it + in terms of object hierarchy. For example, if a policy targets a Service, the + Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and + the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most + useful object to place Policy status on, so we recommend that implementations + SHOULD use Gateway as the PolicyAncestorStatus object unless the designers + have a _very_ good reason otherwise. + + In the context of policy attachment, the Ancestor is used to distinguish which + resource results in a distinct application of this policy. For example, if a policy + targets a Service, it may have a distinct result per attached Gateway. + + Policies targeting the same resource may have different effects depending on the + ancestors of those resources. For example, different Gateways targeting the same + Service may have different capabilities, especially if they have different underlying + implementations. + + For example, in BackendTLSPolicy, the Policy attaches to a Service that is + used as a backend in a HTTPRoute that is itself attached to a Gateway. + In this case, the relevant object for status is the Gateway, and that is the + ancestor object referred to in this status. + + Note that a parent is also an ancestor, so for objects where the parent is the + relevant object for status, this struct SHOULD still be used. + + This struct is intended to be used in a slice that's effectively a map, + with a composite key made up of the AncestorRef and the ControllerName. + properties: + ancestorRef: + description: |- + AncestorRef corresponds with a ParentRef in the spec that this + PolicyAncestorStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: Conditions describes the status of the Policy with + respect to the given Ancestor. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - controllerName + type: object + maxItems: 16 + type: array + required: + - ancestors + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_gatewayclasses.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: gatewayclasses.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: GatewayClass + listKind: GatewayClassList + plural: gatewayclasses + shortNames: + - gc + singular: gatewayclass + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.controllerName + name: Controller + type: string + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.description + name: Description + priority: 1 + type: string + name: v1 + schema: + openAPIV3Schema: + description: |- + GatewayClass describes a class of Gateways available to the user for creating + Gateway resources. + + 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. This recommendation is intended to + limit the blast radius of changes to GatewayClass or associated parameters. + If implementations choose to propagate GatewayClass changes to existing + Gateways, that MUST be clearly documented by the implementation. + + Whenever one or more Gateways are using a GatewayClass, implementations SHOULD + add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the + associated GatewayClass. This ensures that a GatewayClass associated with a + Gateway is not deleted while in use. + + GatewayClass is a Cluster level resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClass. + properties: + controllerName: + description: |- + ControllerName is the name of the controller that is managing Gateways of + this class. The value of this field MUST be a domain prefixed path. + + Example: "example.net/gateway-controller". + + This field is not mutable and cannot be empty. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + description: + description: Description helps describe a GatewayClass with more details. + maxLength: 64 + type: string + parametersRef: + description: |- + ParametersRef is a reference to a resource that contains the configuration + parameters corresponding to the GatewayClass. This is optional if the + controller does not require any additional configuration. + + ParametersRef can reference a standard Kubernetes resource, i.e. ConfigMap, + or an implementation-specific custom resource. The resource can be + cluster-scoped or namespace-scoped. + + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the GatewayClass SHOULD be + rejected with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + + A Gateway for this GatewayClass may provide its own `parametersRef`. When both are specified, + the merging behavior is implementation specific. + It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway. + + Support: Implementation-specific + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. + This field is required when referring to a Namespace-scoped resource and + MUST be unset when referring to a Cluster-scoped resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + required: + - controllerName + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: |- + Status defines the current state of GatewayClass. + + Implementations MUST populate status on all GatewayClass resources which + specify their controller name. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: |- + Conditions is the current status from the controller for + this GatewayClass. + + Controllers should prefer to publish conditions using values + of GatewayClassConditionType for the type of each Condition. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + supportedFeatures: + description: |- + SupportedFeatures is the set of features the GatewayClass support. + It MUST be sorted in ascending alphabetical order by the Name key. + items: + properties: + name: + description: |- + FeatureName is used to describe distinct features that are covered by + conformance tests. + type: string + required: + - name + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.controllerName + name: Controller + type: string + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.description + name: Description + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: |- + GatewayClass describes a class of Gateways available to the user for creating + Gateway resources. + + 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. This recommendation is intended to + limit the blast radius of changes to GatewayClass or associated parameters. + If implementations choose to propagate GatewayClass changes to existing + Gateways, that MUST be clearly documented by the implementation. + + Whenever one or more Gateways are using a GatewayClass, implementations SHOULD + add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the + associated GatewayClass. This ensures that a GatewayClass associated with a + Gateway is not deleted while in use. + + GatewayClass is a Cluster level resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClass. + properties: + controllerName: + description: |- + ControllerName is the name of the controller that is managing Gateways of + this class. The value of this field MUST be a domain prefixed path. + + Example: "example.net/gateway-controller". + + This field is not mutable and cannot be empty. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + description: + description: Description helps describe a GatewayClass with more details. + maxLength: 64 + type: string + parametersRef: + description: |- + ParametersRef is a reference to a resource that contains the configuration + parameters corresponding to the GatewayClass. This is optional if the + controller does not require any additional configuration. + + ParametersRef can reference a standard Kubernetes resource, i.e. ConfigMap, + or an implementation-specific custom resource. The resource can be + cluster-scoped or namespace-scoped. + + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the GatewayClass SHOULD be + rejected with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + + A Gateway for this GatewayClass may provide its own `parametersRef`. When both are specified, + the merging behavior is implementation specific. + It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway. + + Support: Implementation-specific + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. + This field is required when referring to a Namespace-scoped resource and + MUST be unset when referring to a Cluster-scoped resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + required: + - controllerName + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: |- + Status defines the current state of GatewayClass. + + Implementations MUST populate status on all GatewayClass resources which + specify their controller name. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: |- + Conditions is the current status from the controller for + this GatewayClass. + + Controllers should prefer to publish conditions using values + of GatewayClassConditionType for the type of each Condition. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + supportedFeatures: + description: |- + SupportedFeatures is the set of features the GatewayClass support. + It MUST be sorted in ascending alphabetical order by the Name key. + items: + properties: + name: + description: |- + FeatureName is used to describe distinct features that are covered by + conformance tests. + type: string + required: + - name + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_gateways.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: gateways.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: Gateway + listKind: GatewayList + plural: gateways + shortNames: + - gtw + singular: gateway + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.gatewayClassName + name: Class + type: string + - jsonPath: .status.addresses[*].value + name: Address + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + Gateway represents an instance of a service-traffic handling infrastructure + by binding Listeners to a set of IP addresses. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of Gateway. + properties: + addresses: + description: |- + Addresses requested for this Gateway. This is optional and behavior can + depend on the implementation. If a value is set in the spec and the + requested address is invalid or unavailable, the implementation MUST + indicate this in the associated entry in GatewayStatus.Addresses. + + The Addresses field represents a request for the address(es) on the + "outside of the Gateway", that traffic bound for this Gateway will use. + This could be the IP address or hostname of an external load balancer or + other networking infrastructure, or some other address that traffic will + be sent to. + + If no Addresses are specified, the implementation MAY schedule the + Gateway in an implementation-specific manner, assigning an appropriate + set of Addresses. + + The implementation MUST bind all Listeners to every GatewayAddress that + it assigns to the Gateway and add a corresponding entry in + GatewayStatus.Addresses. + + Support: Extended + items: + description: GatewaySpecAddress describes an address that can be + bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: |- + When a value is unspecified, an implementation SHOULD automatically + assign an address matching the requested type if possible. + + If an implementation does not support an empty value, they MUST set the + "Programmed" condition in status to False with a reason of "AddressNotAssigned". + + Examples: `1.2.3.4`, `128::1`, `my-ip-address`. + maxLength: 253 + type: string + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching + ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + true' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: IPAddress values must be unique + rule: 'self.all(a1, a1.type == ''IPAddress'' ? self.exists_one(a2, + a2.type == a1.type && a2.value == a1.value) : true )' + - message: Hostname values must be unique + rule: 'self.all(a1, a1.type == ''Hostname'' ? self.exists_one(a2, + a2.type == a1.type && a2.value == a1.value) : true )' + allowedListeners: + description: |- + AllowedListeners defines which ListenerSets can be attached to this Gateway. + While this feature is experimental, the default value is to allow no ListenerSets. + properties: + namespaces: + default: + from: None + description: |- + Namespaces defines which namespaces ListenerSets can be attached to this Gateway. + While this feature is experimental, the default value is to allow no ListenerSets. + properties: + from: + default: None + description: |- + From indicates where ListenerSets can attach to this Gateway. Possible + values are: + + * Same: Only ListenerSets in the same namespace may be attached to this Gateway. + * Selector: ListenerSets in namespaces selected by the selector may be attached to this Gateway. + * All: ListenerSets in all namespaces may be attached to this Gateway. + * None: Only listeners defined in the Gateway's spec are allowed + + While this feature is experimental, the default value None + enum: + - All + - Selector + - Same + - None + type: string + selector: + description: |- + Selector must be specified when From is set to "Selector". In that case, + only ListenerSets in Namespaces matching this Selector will be selected by this + Gateway. This field is ignored for other values of "From". + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + type: object + type: object + backendTLS: + description: |- + BackendTLS configures TLS settings for when this Gateway is connecting to + backends with TLS. + + Support: Core + properties: + clientCertificateRef: + description: |- + ClientCertificateRef is a reference to an object that contains a Client + Certificate and the associated private key. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + + ClientCertificateRef can reference to standard Kubernetes resources, i.e. + Secret, or implementation-specific custom resources. + + This setting can be overridden on the service level by use of BackendTLSPolicy. + + Support: Core + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + type: object + gatewayClassName: + description: |- + GatewayClassName used for this Gateway. This is the name of a + GatewayClass resource. + maxLength: 253 + minLength: 1 + type: string + infrastructure: + description: |- + Infrastructure defines infrastructure level attributes about this Gateway instance. + + Support: Extended + properties: + annotations: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Annotations that SHOULD be applied to any resources created in response to this Gateway. + + For implementations creating other Kubernetes objects, this should be the `metadata.annotations` field on resources. + For other implementations, this refers to any relevant (implementation specific) "annotations" concepts. + + An implementation may chose to add additional implementation-specific annotations as they see fit. + + Support: Extended + maxProperties: 8 + type: object + x-kubernetes-validations: + - message: Annotation keys must be in the form of an optional + DNS subdomain prefix followed by a required name segment of + up to 63 characters. + rule: self.all(key, key.matches(r"""^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9]$""")) + - message: If specified, the annotation key's prefix must be a + DNS subdomain not longer than 253 characters in total. + rule: self.all(key, key.split("/")[0].size() < 253) + labels: + additionalProperties: + description: |- + LabelValue is the value of a label in the Gateway API. This is used for validation + of maps such as Gateway infrastructure labels. This matches the Kubernetes + label validation rules: + * must be 63 characters or less (can be empty), + * unless empty, must begin and end with an alphanumeric character ([a-z0-9A-Z]), + * could contain dashes (-), underscores (_), dots (.), and alphanumerics between. + + Valid values include: + + * MyValue + * my.name + * 123-my-value + maxLength: 63 + minLength: 0 + pattern: ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$ + type: string + description: |- + Labels that SHOULD be applied to any resources created in response to this Gateway. + + For implementations creating other Kubernetes objects, this should be the `metadata.labels` field on resources. + For other implementations, this refers to any relevant (implementation specific) "labels" concepts. + + An implementation may chose to add additional implementation-specific labels as they see fit. + + If an implementation maps these labels to Pods, or any other resource that would need to be recreated when labels + change, it SHOULD clearly warn about this behavior in documentation. + + Support: Extended + maxProperties: 8 + type: object + x-kubernetes-validations: + - message: Label keys must be in the form of an optional DNS subdomain + prefix followed by a required name segment of up to 63 characters. + rule: self.all(key, key.matches(r"""^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9]$""")) + - message: If specified, the label key's prefix must be a DNS + subdomain not longer than 253 characters in total. + rule: self.all(key, key.split("/")[0].size() < 253) + parametersRef: + description: |- + ParametersRef is a reference to a resource that contains the configuration + parameters corresponding to the Gateway. This is optional if the + controller does not require any additional configuration. + + This follows the same semantics as GatewayClass's `parametersRef`, but on a per-Gateway basis + + The Gateway's GatewayClass may provide its own `parametersRef`. When both are specified, + the merging behavior is implementation specific. + It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway. + + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the Gateway SHOULD be + rejected with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + + Support: Implementation-specific + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + type: object + listeners: + description: |- + Listeners associated with this Gateway. Listeners define + logical endpoints that are bound on this Gateway's addresses. + At least one Listener MUST be specified. + + ## Distinct Listeners + + Each Listener in a set of Listeners (for example, in a single Gateway) + MUST be _distinct_, in that a traffic flow MUST be able to be assigned to + exactly one listener. (This section uses "set of Listeners" rather than + "Listeners in a single Gateway" because implementations MAY merge configuration + from multiple Gateways onto a single data plane, and these rules _also_ + apply in that case). + + Practically, this means that each listener in a set MUST have a unique + combination of Port, Protocol, and, if supported by the protocol, Hostname. + + Some combinations of port, protocol, and TLS settings are considered + Core support and MUST be supported by implementations based on the objects + they support: + + HTTPRoute + + 1. HTTPRoute, Port: 80, Protocol: HTTP + 2. HTTPRoute, Port: 443, Protocol: HTTPS, TLS Mode: Terminate, TLS keypair provided + + TLSRoute + + 1. TLSRoute, Port: 443, Protocol: TLS, TLS Mode: Passthrough + + "Distinct" Listeners have the following property: + + **The implementation can match inbound requests to a single distinct + Listener**. + + When multiple Listeners share values for fields (for + example, two Listeners with the same Port value), the implementation + can match requests to only one of the Listeners using other + Listener fields. + + When multiple listeners have the same value for the Protocol field, then + each of the Listeners with matching Protocol values MUST have different + values for other fields. + + The set of fields that MUST be different for a Listener differs per protocol. + The following rules define the rules for what fields MUST be considered for + Listeners to be distinct with each protocol currently defined in the + Gateway API spec. + + The set of listeners that all share a protocol value MUST have _different_ + values for _at least one_ of these fields to be distinct: + + * **HTTP, HTTPS, TLS**: Port, Hostname + * **TCP, UDP**: Port + + One **very** important rule to call out involves what happens when an + implementation: + + * Supports TCP protocol Listeners, as well as HTTP, HTTPS, or TLS protocol + Listeners, and + * sees HTTP, HTTPS, or TLS protocols with the same `port` as one with TCP + Protocol. + + In this case all the Listeners that share a port with the + TCP Listener are not distinct and so MUST NOT be accepted. + + If an implementation does not support TCP Protocol Listeners, then the + previous rule does not apply, and the TCP Listeners SHOULD NOT be + accepted. + + Note that the `tls` field is not used for determining if a listener is distinct, because + Listeners that _only_ differ on TLS config will still conflict in all cases. + + ### Listeners that are distinct only by Hostname + + When the Listeners are distinct based only on Hostname, inbound request + hostnames MUST match from the most specific to least specific Hostname + values to choose the correct Listener and its associated set of Routes. + + Exact matches MUST be processed before wildcard matches, and wildcard + matches MUST be processed before fallback (empty Hostname value) + matches. For example, `"foo.example.com"` takes precedence over + `"*.example.com"`, and `"*.example.com"` takes precedence over `""`. + + Additionally, if there are multiple wildcard entries, more specific + wildcard entries must be processed before less specific wildcard entries. + For example, `"*.foo.example.com"` takes precedence over `"*.example.com"`. + + The precise definition here is that the higher the number of dots in the + hostname to the right of the wildcard character, the higher the precedence. + + The wildcard character will match any number of characters _and dots_ to + the left, however, so `"*.example.com"` will match both + `"foo.bar.example.com"` _and_ `"bar.example.com"`. + + ## Handling indistinct Listeners + + If a set of Listeners contains Listeners that are not distinct, then those + Listeners are _Conflicted_, and the implementation MUST set the "Conflicted" + condition in the Listener Status to "True". + + The words "indistinct" and "conflicted" are considered equivalent for the + purpose of this documentation. + + Implementations MAY choose to accept a Gateway with some Conflicted + Listeners only if they only accept the partial Listener set that contains + no Conflicted Listeners. + + Specifically, an implementation MAY accept a partial Listener set subject to + the following rules: + + * The implementation MUST NOT pick one conflicting Listener as the winner. + ALL indistinct Listeners must not be accepted for processing. + * At least one distinct Listener MUST be present, or else the Gateway effectively + contains _no_ Listeners, and must be rejected from processing as a whole. + + The implementation MUST set a "ListenersNotValid" condition on the + Gateway Status when the Gateway contains Conflicted Listeners whether or + not they accept the Gateway. That Condition SHOULD clearly + indicate in the Message which Listeners are conflicted, and which are + Accepted. Additionally, the Listener status for those listeners SHOULD + indicate which Listeners are conflicted and not Accepted. + + ## General Listener behavior + + Note that, for all distinct Listeners, requests SHOULD match at most one Listener. + For example, if Listeners are defined for "foo.example.com" and "*.example.com", a + request to "foo.example.com" SHOULD only be routed using routes attached + to the "foo.example.com" Listener (and not the "*.example.com" Listener). + + This concept is known as "Listener Isolation", and it is an Extended feature + of Gateway API. Implementations that do not support Listener Isolation MUST + clearly document this, and MUST NOT claim support for the + `GatewayHTTPListenerIsolation` feature. + + Implementations that _do_ support Listener Isolation SHOULD claim support + for the Extended `GatewayHTTPListenerIsolation` feature and pass the associated + conformance tests. + + ## Compatible Listeners + + A Gateway's Listeners are considered _compatible_ if: + + 1. They are distinct. + 2. The implementation can serve them in compliance with the Addresses + requirement that all Listeners are available on all assigned + addresses. + + Compatible combinations in Extended support are expected to vary across + implementations. A combination that is compatible for one implementation + may not be compatible for another. + + For example, an implementation that cannot serve both TCP and UDP listeners + on the same address, or cannot mix HTTPS and generic TLS listens on the same port + would not consider those cases compatible, even though they are distinct. + + Implementations MAY merge separate Gateways onto a single set of + Addresses if all Listeners across all Gateways are compatible. + + In a future release the MinItems=1 requirement MAY be dropped. + + Support: Core + items: + description: |- + Listener embodies the concept of a logical endpoint where a Gateway accepts + network connections. + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: |- + AllowedRoutes defines the types of routes that MAY be attached to a + Listener and the trusted namespaces where those Route resources MAY be + present. + + Although a client request may match multiple route rules, only one rule + may ultimately receive the request. Matching precedence MUST be + determined in order of the following criteria: + + * The most specific match as defined by the Route type. + * The oldest Route based on creation timestamp. For example, a Route with + a creation timestamp of "2020-09-08 01:02:03" is given precedence over + a Route with a creation timestamp of "2020-09-08 01:02:04". + * If everything else is equivalent, the Route appearing first in + alphabetical order (namespace/name) should be given precedence. For + example, foo/bar is given precedence over foo/baz. + + All valid rules within a Route attached to this Listener should be + implemented. Invalid Route rules can be ignored (sometimes that will mean + the full Route). If a Route rule transitions from valid to invalid, + support for that Route rule should be dropped to ensure consistency. For + example, even if a filter specified by a Route rule is invalid, the rest + of the rules within that Route should still be supported. + + Support: Core + properties: + kinds: + description: |- + Kinds specifies the groups and kinds of Routes that are allowed to bind + to this Gateway Listener. When unspecified or empty, the kinds of Routes + selected are determined using the Listener protocol. + + A RouteGroupKind MUST correspond to kinds of Routes that are compatible + with the application protocol specified in the Listener's Protocol field. + If an implementation does not support or recognize this resource type, it + MUST set the "ResolvedRefs" condition to False for this Listener with the + "InvalidRouteKinds" reason. + + Support: Core + items: + description: RouteGroupKind indicates the group and kind + of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: |- + Namespaces indicates namespaces from which Routes may be attached to this + Listener. This is restricted to the namespace of this Gateway by default. + + Support: Core + properties: + from: + default: Same + description: |- + From indicates where Routes will be selected for this Gateway. Possible + values are: + + * All: Routes in all namespaces may be used by this Gateway. + * Selector: Routes in namespaces selected by the selector may be used by + this Gateway. + * Same: Only Routes in the same namespace may be used by this Gateway. + + Support: Core + enum: + - All + - Selector + - Same + type: string + selector: + description: |- + Selector must be specified when From is set to "Selector". In that case, + only Routes in Namespaces matching this Selector will be selected by this + Gateway. This field is ignored for other values of "From". + + Support: Core + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + type: object + type: object + hostname: + description: |- + Hostname specifies the virtual hostname to match for protocol types that + define this concept. When unspecified, all hostnames are matched. This + field is ignored for protocols that don't require hostname based + matching. + + Implementations MUST apply Hostname matching appropriately for each of + the following protocols: + + * TLS: The Listener Hostname MUST match the SNI. + * HTTP: The Listener Hostname MUST match the Host header of the request. + * HTTPS: The Listener Hostname SHOULD match both the SNI and Host header. + Note that this does not require the SNI and Host header to be the same. + The semantics of this are described in more detail below. + + To ensure security, Section 11.1 of RFC-6066 emphasizes that server + implementations that rely on SNI hostname matching MUST also verify + hostnames within the application protocol. + + Section 9.1.2 of RFC-7540 provides a mechanism for servers to reject the + reuse of a connection by responding with the HTTP 421 Misdirected Request + status code. This indicates that the origin server has rejected the + request because it appears to have been misdirected. + + To detect misdirected requests, Gateways SHOULD match the authority of + the requests with all the SNI hostname(s) configured across all the + Gateway Listeners on the same port and protocol: + + * If another Listener has an exact match or more specific wildcard entry, + the Gateway SHOULD return a 421. + * If the current Listener (selected by SNI matching during ClientHello) + does not match the Host: + * If another Listener does match the Host the Gateway SHOULD return a + 421. + * If no other Listener matches the Host, the Gateway MUST return a + 404. + + For HTTPRoute and TLSRoute resources, there is an interaction with the + `spec.hostnames` array. When both listener and route specify hostnames, + there MUST be an intersection between the values for a Route to be + accepted. For more information, refer to the Route specific Hostnames + documentation. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + name: + description: |- + Name is the name of the Listener. This name MUST be unique within a + Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: |- + Port is the network port. Multiple listeners may use the + same port, subject to the Listener compatibility rules. + + Support: Core + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: |- + Protocol specifies the network protocol this listener expects to receive. + + Support: Core + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: |- + TLS is the TLS configuration for the Listener. This field is required if + the Protocol field is "HTTPS" or "TLS". It is invalid to set this field + if the Protocol field is "HTTP", "TCP", or "UDP". + + The association of SNIs to Certificate defined in GatewayTLSConfig is + defined based on the Hostname field for this listener. + + The GatewayClass MUST use the longest matching SNI out of all + available certificates for any TLS handshake. + + Support: Core + properties: + certificateRefs: + description: |- + CertificateRefs contains a series of references to Kubernetes objects that + contains TLS certificates and private keys. These certificates are used to + establish a TLS handshake for requests that match the hostname of the + associated listener. + + A single CertificateRef to a Kubernetes Secret has "Core" support. + Implementations MAY choose to support attaching multiple certificates to + a Listener, but this behavior is implementation-specific. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + + This field is required to have at least one element when the mode is set + to "Terminate" (default) and is optional otherwise. + + CertificateRefs can reference to standard Kubernetes resources, i.e. + Secret, or implementation-specific custom resources. + + Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls + + Support: Implementation-specific (More than one reference or other resource types) + items: + description: |- + SecretObjectReference identifies an API object including its namespace, + defaulting to Secret. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example + "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + frontendValidation: + description: |- + FrontendValidation holds configuration information for validating the frontend (client). + Setting this field will require clients to send a client certificate + required for validation during the TLS handshake. In browsers this may result in a dialog appearing + that requests a user to specify the client certificate. + The maximum depth of a certificate chain accepted in verification is Implementation specific. + + Support: Extended + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to + Kubernetes objects that contain TLS certificates of + the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the client. + + A single CA certificate reference to a Kubernetes ConfigMap + has "Core" support. + Implementations MAY choose to support attaching multiple CA certificates to + a Listener, but this behavior is implementation-specific. + + Support: Core - A single reference to a Kubernetes ConfigMap + with the CA certificate in a key named `ca.crt`. + + Support: Implementation-specific (More than one reference, or other kinds + of resources). + + References to a resource in a different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + items: + description: |- + ObjectReference identifies an API object including its namespace. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When set to the empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "ConfigMap" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + maxItems: 8 + minItems: 1 + type: array + type: object + mode: + default: Terminate + description: |- + Mode defines the TLS behavior for the TLS session initiated by the client. + There are two possible modes: + + - Terminate: The TLS session between the downstream client and the + Gateway is terminated at the Gateway. This mode requires certificates + to be specified in some way, such as populating the certificateRefs + field. + - Passthrough: The TLS session is NOT terminated by the Gateway. This + implies that the Gateway can't decipher the TLS stream except for + the ClientHello message of the TLS protocol. The certificateRefs field + is ignored in this mode. + + Support: Core + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Options are a list of key/value pairs to enable extended TLS + configuration for each implementation. For example, configuring the + minimum TLS version or supported cipher suites. + + A set of common keys MAY be defined by the API in the future. To avoid + any ambiguity, implementation-specific definitions MUST use + domain-prefixed names, such as `example.com/my-custom-option`. + Un-prefixed names are reserved for key names defined by Gateway API. + + Support: Implementation-specific + maxProperties: 16 + type: object + type: object + x-kubernetes-validations: + - message: certificateRefs or options must be specified when + mode is Terminate + rule: 'self.mode == ''Terminate'' ? size(self.certificateRefs) + > 0 || size(self.options) > 0 : true' + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: tls must not be specified for protocols ['HTTP', 'TCP', + 'UDP'] + rule: 'self.all(l, l.protocol in [''HTTP'', ''TCP'', ''UDP''] ? + !has(l.tls) : true)' + - message: tls mode must be Terminate for protocol HTTPS + rule: 'self.all(l, (l.protocol == ''HTTPS'' && has(l.tls)) ? (l.tls.mode + == '''' || l.tls.mode == ''Terminate'') : true)' + - message: hostname must not be specified for protocols ['TCP', 'UDP'] + rule: 'self.all(l, l.protocol in [''TCP'', ''UDP''] ? (!has(l.hostname) + || l.hostname == '''') : true)' + - message: Listener name must be unique within the Gateway + rule: self.all(l1, self.exists_one(l2, l1.name == l2.name)) + - message: Combination of port, protocol and hostname must be unique + for each listener + rule: 'self.all(l1, self.exists_one(l2, l1.port == l2.port && l1.protocol + == l2.protocol && (has(l1.hostname) && has(l2.hostname) ? l1.hostname + == l2.hostname : !has(l1.hostname) && !has(l2.hostname))))' + required: + - gatewayClassName + - listeners + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: Status defines the current state of Gateway. + properties: + addresses: + description: |- + Addresses lists the network addresses that have been bound to the + Gateway. + + This list may differ from the addresses provided in the spec under some + conditions: + + * no addresses are specified, all addresses are dynamically assigned + * a combination of specified and dynamic addresses are assigned + * a specified address was unusable (e.g. already in use) + items: + description: GatewayStatusAddress describes a network address that + is bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: |- + Value of the address. The validity of the values will depend + on the type and support by the controller. + + Examples: `1.2.3.4`, `128::1`, `my-ip-address`. + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching + ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + true' + maxItems: 16 + type: array + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: |- + Conditions describe the current conditions of the Gateway. + + Implementations should prefer to express Gateway conditions + using the `GatewayConditionType` and `GatewayConditionReason` + constants so that operators and tools can converge on a common + vocabulary to describe Gateway state. + + Known condition types are: + + * "Accepted" + * "Programmed" + * "Ready" + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port + defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: |- + AttachedRoutes represents the total number of Routes that have been + successfully attached to this Listener. + + Successful attachment of a Route to a Listener is based solely on the + combination of the AllowedRoutes field on the corresponding Listener + and the Route's ParentRefs field. A Route is successfully attached to + a Listener when it is selected by the Listener's AllowedRoutes field + AND the Route has a valid ParentRef selecting the whole Gateway + resource or a specific Listener as a parent resource (more detail on + attachment semantics can be found in the documentation on the various + Route kinds ParentRefs fields). Listener or Route status does not impact + successful attachment, i.e. the AttachedRoutes field count MUST be set + for Listeners with condition Accepted: false and MUST count successfully + attached Routes that may themselves have Accepted: false conditions. + + Uses for this field include troubleshooting Route attachment and + measuring blast radius/impact of changes to a Listener. + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this + listener. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status + corresponds to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + supportedKinds: + description: |- + SupportedKinds is the list indicating the Kinds supported by this + listener. This MUST represent the kinds an implementation supports for + that Listener configuration. + + If kinds are specified in Spec that are not supported, they MUST NOT + appear in this list and an implementation MUST set the "ResolvedRefs" + condition to "False" with the "InvalidRouteKinds" reason. If both valid + and invalid Route kinds are specified, the implementation MUST + reference the valid Route kinds that have been specified. + items: + description: RouteGroupKind indicates the group and kind of + a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.gatewayClassName + name: Class + type: string + - jsonPath: .status.addresses[*].value + name: Address + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: |- + Gateway represents an instance of a service-traffic handling infrastructure + by binding Listeners to a set of IP addresses. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of Gateway. + properties: + addresses: + description: |- + Addresses requested for this Gateway. This is optional and behavior can + depend on the implementation. If a value is set in the spec and the + requested address is invalid or unavailable, the implementation MUST + indicate this in the associated entry in GatewayStatus.Addresses. + + The Addresses field represents a request for the address(es) on the + "outside of the Gateway", that traffic bound for this Gateway will use. + This could be the IP address or hostname of an external load balancer or + other networking infrastructure, or some other address that traffic will + be sent to. + + If no Addresses are specified, the implementation MAY schedule the + Gateway in an implementation-specific manner, assigning an appropriate + set of Addresses. + + The implementation MUST bind all Listeners to every GatewayAddress that + it assigns to the Gateway and add a corresponding entry in + GatewayStatus.Addresses. + + Support: Extended + items: + description: GatewaySpecAddress describes an address that can be + bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: |- + When a value is unspecified, an implementation SHOULD automatically + assign an address matching the requested type if possible. + + If an implementation does not support an empty value, they MUST set the + "Programmed" condition in status to False with a reason of "AddressNotAssigned". + + Examples: `1.2.3.4`, `128::1`, `my-ip-address`. + maxLength: 253 + type: string + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching + ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + true' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: IPAddress values must be unique + rule: 'self.all(a1, a1.type == ''IPAddress'' ? self.exists_one(a2, + a2.type == a1.type && a2.value == a1.value) : true )' + - message: Hostname values must be unique + rule: 'self.all(a1, a1.type == ''Hostname'' ? self.exists_one(a2, + a2.type == a1.type && a2.value == a1.value) : true )' + allowedListeners: + description: |- + AllowedListeners defines which ListenerSets can be attached to this Gateway. + While this feature is experimental, the default value is to allow no ListenerSets. + properties: + namespaces: + default: + from: None + description: |- + Namespaces defines which namespaces ListenerSets can be attached to this Gateway. + While this feature is experimental, the default value is to allow no ListenerSets. + properties: + from: + default: None + description: |- + From indicates where ListenerSets can attach to this Gateway. Possible + values are: + + * Same: Only ListenerSets in the same namespace may be attached to this Gateway. + * Selector: ListenerSets in namespaces selected by the selector may be attached to this Gateway. + * All: ListenerSets in all namespaces may be attached to this Gateway. + * None: Only listeners defined in the Gateway's spec are allowed + + While this feature is experimental, the default value None + enum: + - All + - Selector + - Same + - None + type: string + selector: + description: |- + Selector must be specified when From is set to "Selector". In that case, + only ListenerSets in Namespaces matching this Selector will be selected by this + Gateway. This field is ignored for other values of "From". + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + type: object + type: object + backendTLS: + description: |- + BackendTLS configures TLS settings for when this Gateway is connecting to + backends with TLS. + + Support: Core + properties: + clientCertificateRef: + description: |- + ClientCertificateRef is a reference to an object that contains a Client + Certificate and the associated private key. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + + ClientCertificateRef can reference to standard Kubernetes resources, i.e. + Secret, or implementation-specific custom resources. + + This setting can be overridden on the service level by use of BackendTLSPolicy. + + Support: Core + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + type: object + gatewayClassName: + description: |- + GatewayClassName used for this Gateway. This is the name of a + GatewayClass resource. + maxLength: 253 + minLength: 1 + type: string + infrastructure: + description: |- + Infrastructure defines infrastructure level attributes about this Gateway instance. + + Support: Extended + properties: + annotations: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Annotations that SHOULD be applied to any resources created in response to this Gateway. + + For implementations creating other Kubernetes objects, this should be the `metadata.annotations` field on resources. + For other implementations, this refers to any relevant (implementation specific) "annotations" concepts. + + An implementation may chose to add additional implementation-specific annotations as they see fit. + + Support: Extended + maxProperties: 8 + type: object + x-kubernetes-validations: + - message: Annotation keys must be in the form of an optional + DNS subdomain prefix followed by a required name segment of + up to 63 characters. + rule: self.all(key, key.matches(r"""^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9]$""")) + - message: If specified, the annotation key's prefix must be a + DNS subdomain not longer than 253 characters in total. + rule: self.all(key, key.split("/")[0].size() < 253) + labels: + additionalProperties: + description: |- + LabelValue is the value of a label in the Gateway API. This is used for validation + of maps such as Gateway infrastructure labels. This matches the Kubernetes + label validation rules: + * must be 63 characters or less (can be empty), + * unless empty, must begin and end with an alphanumeric character ([a-z0-9A-Z]), + * could contain dashes (-), underscores (_), dots (.), and alphanumerics between. + + Valid values include: + + * MyValue + * my.name + * 123-my-value + maxLength: 63 + minLength: 0 + pattern: ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$ + type: string + description: |- + Labels that SHOULD be applied to any resources created in response to this Gateway. + + For implementations creating other Kubernetes objects, this should be the `metadata.labels` field on resources. + For other implementations, this refers to any relevant (implementation specific) "labels" concepts. + + An implementation may chose to add additional implementation-specific labels as they see fit. + + If an implementation maps these labels to Pods, or any other resource that would need to be recreated when labels + change, it SHOULD clearly warn about this behavior in documentation. + + Support: Extended + maxProperties: 8 + type: object + x-kubernetes-validations: + - message: Label keys must be in the form of an optional DNS subdomain + prefix followed by a required name segment of up to 63 characters. + rule: self.all(key, key.matches(r"""^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9]$""")) + - message: If specified, the label key's prefix must be a DNS + subdomain not longer than 253 characters in total. + rule: self.all(key, key.split("/")[0].size() < 253) + parametersRef: + description: |- + ParametersRef is a reference to a resource that contains the configuration + parameters corresponding to the Gateway. This is optional if the + controller does not require any additional configuration. + + This follows the same semantics as GatewayClass's `parametersRef`, but on a per-Gateway basis + + The Gateway's GatewayClass may provide its own `parametersRef`. When both are specified, + the merging behavior is implementation specific. + It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway. + + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the Gateway SHOULD be + rejected with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + + Support: Implementation-specific + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + type: object + listeners: + description: |- + Listeners associated with this Gateway. Listeners define + logical endpoints that are bound on this Gateway's addresses. + At least one Listener MUST be specified. + + ## Distinct Listeners + + Each Listener in a set of Listeners (for example, in a single Gateway) + MUST be _distinct_, in that a traffic flow MUST be able to be assigned to + exactly one listener. (This section uses "set of Listeners" rather than + "Listeners in a single Gateway" because implementations MAY merge configuration + from multiple Gateways onto a single data plane, and these rules _also_ + apply in that case). + + Practically, this means that each listener in a set MUST have a unique + combination of Port, Protocol, and, if supported by the protocol, Hostname. + + Some combinations of port, protocol, and TLS settings are considered + Core support and MUST be supported by implementations based on the objects + they support: + + HTTPRoute + + 1. HTTPRoute, Port: 80, Protocol: HTTP + 2. HTTPRoute, Port: 443, Protocol: HTTPS, TLS Mode: Terminate, TLS keypair provided + + TLSRoute + + 1. TLSRoute, Port: 443, Protocol: TLS, TLS Mode: Passthrough + + "Distinct" Listeners have the following property: + + **The implementation can match inbound requests to a single distinct + Listener**. + + When multiple Listeners share values for fields (for + example, two Listeners with the same Port value), the implementation + can match requests to only one of the Listeners using other + Listener fields. + + When multiple listeners have the same value for the Protocol field, then + each of the Listeners with matching Protocol values MUST have different + values for other fields. + + The set of fields that MUST be different for a Listener differs per protocol. + The following rules define the rules for what fields MUST be considered for + Listeners to be distinct with each protocol currently defined in the + Gateway API spec. + + The set of listeners that all share a protocol value MUST have _different_ + values for _at least one_ of these fields to be distinct: + + * **HTTP, HTTPS, TLS**: Port, Hostname + * **TCP, UDP**: Port + + One **very** important rule to call out involves what happens when an + implementation: + + * Supports TCP protocol Listeners, as well as HTTP, HTTPS, or TLS protocol + Listeners, and + * sees HTTP, HTTPS, or TLS protocols with the same `port` as one with TCP + Protocol. + + In this case all the Listeners that share a port with the + TCP Listener are not distinct and so MUST NOT be accepted. + + If an implementation does not support TCP Protocol Listeners, then the + previous rule does not apply, and the TCP Listeners SHOULD NOT be + accepted. + + Note that the `tls` field is not used for determining if a listener is distinct, because + Listeners that _only_ differ on TLS config will still conflict in all cases. + + ### Listeners that are distinct only by Hostname + + When the Listeners are distinct based only on Hostname, inbound request + hostnames MUST match from the most specific to least specific Hostname + values to choose the correct Listener and its associated set of Routes. + + Exact matches MUST be processed before wildcard matches, and wildcard + matches MUST be processed before fallback (empty Hostname value) + matches. For example, `"foo.example.com"` takes precedence over + `"*.example.com"`, and `"*.example.com"` takes precedence over `""`. + + Additionally, if there are multiple wildcard entries, more specific + wildcard entries must be processed before less specific wildcard entries. + For example, `"*.foo.example.com"` takes precedence over `"*.example.com"`. + + The precise definition here is that the higher the number of dots in the + hostname to the right of the wildcard character, the higher the precedence. + + The wildcard character will match any number of characters _and dots_ to + the left, however, so `"*.example.com"` will match both + `"foo.bar.example.com"` _and_ `"bar.example.com"`. + + ## Handling indistinct Listeners + + If a set of Listeners contains Listeners that are not distinct, then those + Listeners are _Conflicted_, and the implementation MUST set the "Conflicted" + condition in the Listener Status to "True". + + The words "indistinct" and "conflicted" are considered equivalent for the + purpose of this documentation. + + Implementations MAY choose to accept a Gateway with some Conflicted + Listeners only if they only accept the partial Listener set that contains + no Conflicted Listeners. + + Specifically, an implementation MAY accept a partial Listener set subject to + the following rules: + + * The implementation MUST NOT pick one conflicting Listener as the winner. + ALL indistinct Listeners must not be accepted for processing. + * At least one distinct Listener MUST be present, or else the Gateway effectively + contains _no_ Listeners, and must be rejected from processing as a whole. + + The implementation MUST set a "ListenersNotValid" condition on the + Gateway Status when the Gateway contains Conflicted Listeners whether or + not they accept the Gateway. That Condition SHOULD clearly + indicate in the Message which Listeners are conflicted, and which are + Accepted. Additionally, the Listener status for those listeners SHOULD + indicate which Listeners are conflicted and not Accepted. + + ## General Listener behavior + + Note that, for all distinct Listeners, requests SHOULD match at most one Listener. + For example, if Listeners are defined for "foo.example.com" and "*.example.com", a + request to "foo.example.com" SHOULD only be routed using routes attached + to the "foo.example.com" Listener (and not the "*.example.com" Listener). + + This concept is known as "Listener Isolation", and it is an Extended feature + of Gateway API. Implementations that do not support Listener Isolation MUST + clearly document this, and MUST NOT claim support for the + `GatewayHTTPListenerIsolation` feature. + + Implementations that _do_ support Listener Isolation SHOULD claim support + for the Extended `GatewayHTTPListenerIsolation` feature and pass the associated + conformance tests. + + ## Compatible Listeners + + A Gateway's Listeners are considered _compatible_ if: + + 1. They are distinct. + 2. The implementation can serve them in compliance with the Addresses + requirement that all Listeners are available on all assigned + addresses. + + Compatible combinations in Extended support are expected to vary across + implementations. A combination that is compatible for one implementation + may not be compatible for another. + + For example, an implementation that cannot serve both TCP and UDP listeners + on the same address, or cannot mix HTTPS and generic TLS listens on the same port + would not consider those cases compatible, even though they are distinct. + + Implementations MAY merge separate Gateways onto a single set of + Addresses if all Listeners across all Gateways are compatible. + + In a future release the MinItems=1 requirement MAY be dropped. + + Support: Core + items: + description: |- + Listener embodies the concept of a logical endpoint where a Gateway accepts + network connections. + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: |- + AllowedRoutes defines the types of routes that MAY be attached to a + Listener and the trusted namespaces where those Route resources MAY be + present. + + Although a client request may match multiple route rules, only one rule + may ultimately receive the request. Matching precedence MUST be + determined in order of the following criteria: + + * The most specific match as defined by the Route type. + * The oldest Route based on creation timestamp. For example, a Route with + a creation timestamp of "2020-09-08 01:02:03" is given precedence over + a Route with a creation timestamp of "2020-09-08 01:02:04". + * If everything else is equivalent, the Route appearing first in + alphabetical order (namespace/name) should be given precedence. For + example, foo/bar is given precedence over foo/baz. + + All valid rules within a Route attached to this Listener should be + implemented. Invalid Route rules can be ignored (sometimes that will mean + the full Route). If a Route rule transitions from valid to invalid, + support for that Route rule should be dropped to ensure consistency. For + example, even if a filter specified by a Route rule is invalid, the rest + of the rules within that Route should still be supported. + + Support: Core + properties: + kinds: + description: |- + Kinds specifies the groups and kinds of Routes that are allowed to bind + to this Gateway Listener. When unspecified or empty, the kinds of Routes + selected are determined using the Listener protocol. + + A RouteGroupKind MUST correspond to kinds of Routes that are compatible + with the application protocol specified in the Listener's Protocol field. + If an implementation does not support or recognize this resource type, it + MUST set the "ResolvedRefs" condition to False for this Listener with the + "InvalidRouteKinds" reason. + + Support: Core + items: + description: RouteGroupKind indicates the group and kind + of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: |- + Namespaces indicates namespaces from which Routes may be attached to this + Listener. This is restricted to the namespace of this Gateway by default. + + Support: Core + properties: + from: + default: Same + description: |- + From indicates where Routes will be selected for this Gateway. Possible + values are: + + * All: Routes in all namespaces may be used by this Gateway. + * Selector: Routes in namespaces selected by the selector may be used by + this Gateway. + * Same: Only Routes in the same namespace may be used by this Gateway. + + Support: Core + enum: + - All + - Selector + - Same + type: string + selector: + description: |- + Selector must be specified when From is set to "Selector". In that case, + only Routes in Namespaces matching this Selector will be selected by this + Gateway. This field is ignored for other values of "From". + + Support: Core + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + type: object + type: object + hostname: + description: |- + Hostname specifies the virtual hostname to match for protocol types that + define this concept. When unspecified, all hostnames are matched. This + field is ignored for protocols that don't require hostname based + matching. + + Implementations MUST apply Hostname matching appropriately for each of + the following protocols: + + * TLS: The Listener Hostname MUST match the SNI. + * HTTP: The Listener Hostname MUST match the Host header of the request. + * HTTPS: The Listener Hostname SHOULD match both the SNI and Host header. + Note that this does not require the SNI and Host header to be the same. + The semantics of this are described in more detail below. + + To ensure security, Section 11.1 of RFC-6066 emphasizes that server + implementations that rely on SNI hostname matching MUST also verify + hostnames within the application protocol. + + Section 9.1.2 of RFC-7540 provides a mechanism for servers to reject the + reuse of a connection by responding with the HTTP 421 Misdirected Request + status code. This indicates that the origin server has rejected the + request because it appears to have been misdirected. + + To detect misdirected requests, Gateways SHOULD match the authority of + the requests with all the SNI hostname(s) configured across all the + Gateway Listeners on the same port and protocol: + + * If another Listener has an exact match or more specific wildcard entry, + the Gateway SHOULD return a 421. + * If the current Listener (selected by SNI matching during ClientHello) + does not match the Host: + * If another Listener does match the Host the Gateway SHOULD return a + 421. + * If no other Listener matches the Host, the Gateway MUST return a + 404. + + For HTTPRoute and TLSRoute resources, there is an interaction with the + `spec.hostnames` array. When both listener and route specify hostnames, + there MUST be an intersection between the values for a Route to be + accepted. For more information, refer to the Route specific Hostnames + documentation. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + name: + description: |- + Name is the name of the Listener. This name MUST be unique within a + Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: |- + Port is the network port. Multiple listeners may use the + same port, subject to the Listener compatibility rules. + + Support: Core + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: |- + Protocol specifies the network protocol this listener expects to receive. + + Support: Core + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: |- + TLS is the TLS configuration for the Listener. This field is required if + the Protocol field is "HTTPS" or "TLS". It is invalid to set this field + if the Protocol field is "HTTP", "TCP", or "UDP". + + The association of SNIs to Certificate defined in GatewayTLSConfig is + defined based on the Hostname field for this listener. + + The GatewayClass MUST use the longest matching SNI out of all + available certificates for any TLS handshake. + + Support: Core + properties: + certificateRefs: + description: |- + CertificateRefs contains a series of references to Kubernetes objects that + contains TLS certificates and private keys. These certificates are used to + establish a TLS handshake for requests that match the hostname of the + associated listener. + + A single CertificateRef to a Kubernetes Secret has "Core" support. + Implementations MAY choose to support attaching multiple certificates to + a Listener, but this behavior is implementation-specific. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + + This field is required to have at least one element when the mode is set + to "Terminate" (default) and is optional otherwise. + + CertificateRefs can reference to standard Kubernetes resources, i.e. + Secret, or implementation-specific custom resources. + + Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls + + Support: Implementation-specific (More than one reference or other resource types) + items: + description: |- + SecretObjectReference identifies an API object including its namespace, + defaulting to Secret. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example + "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + frontendValidation: + description: |- + FrontendValidation holds configuration information for validating the frontend (client). + Setting this field will require clients to send a client certificate + required for validation during the TLS handshake. In browsers this may result in a dialog appearing + that requests a user to specify the client certificate. + The maximum depth of a certificate chain accepted in verification is Implementation specific. + + Support: Extended + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to + Kubernetes objects that contain TLS certificates of + the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the client. + + A single CA certificate reference to a Kubernetes ConfigMap + has "Core" support. + Implementations MAY choose to support attaching multiple CA certificates to + a Listener, but this behavior is implementation-specific. + + Support: Core - A single reference to a Kubernetes ConfigMap + with the CA certificate in a key named `ca.crt`. + + Support: Implementation-specific (More than one reference, or other kinds + of resources). + + References to a resource in a different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + items: + description: |- + ObjectReference identifies an API object including its namespace. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When set to the empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "ConfigMap" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + maxItems: 8 + minItems: 1 + type: array + type: object + mode: + default: Terminate + description: |- + Mode defines the TLS behavior for the TLS session initiated by the client. + There are two possible modes: + + - Terminate: The TLS session between the downstream client and the + Gateway is terminated at the Gateway. This mode requires certificates + to be specified in some way, such as populating the certificateRefs + field. + - Passthrough: The TLS session is NOT terminated by the Gateway. This + implies that the Gateway can't decipher the TLS stream except for + the ClientHello message of the TLS protocol. The certificateRefs field + is ignored in this mode. + + Support: Core + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Options are a list of key/value pairs to enable extended TLS + configuration for each implementation. For example, configuring the + minimum TLS version or supported cipher suites. + + A set of common keys MAY be defined by the API in the future. To avoid + any ambiguity, implementation-specific definitions MUST use + domain-prefixed names, such as `example.com/my-custom-option`. + Un-prefixed names are reserved for key names defined by Gateway API. + + Support: Implementation-specific + maxProperties: 16 + type: object + type: object + x-kubernetes-validations: + - message: certificateRefs or options must be specified when + mode is Terminate + rule: 'self.mode == ''Terminate'' ? size(self.certificateRefs) + > 0 || size(self.options) > 0 : true' + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: tls must not be specified for protocols ['HTTP', 'TCP', + 'UDP'] + rule: 'self.all(l, l.protocol in [''HTTP'', ''TCP'', ''UDP''] ? + !has(l.tls) : true)' + - message: tls mode must be Terminate for protocol HTTPS + rule: 'self.all(l, (l.protocol == ''HTTPS'' && has(l.tls)) ? (l.tls.mode + == '''' || l.tls.mode == ''Terminate'') : true)' + - message: hostname must not be specified for protocols ['TCP', 'UDP'] + rule: 'self.all(l, l.protocol in [''TCP'', ''UDP''] ? (!has(l.hostname) + || l.hostname == '''') : true)' + - message: Listener name must be unique within the Gateway + rule: self.all(l1, self.exists_one(l2, l1.name == l2.name)) + - message: Combination of port, protocol and hostname must be unique + for each listener + rule: 'self.all(l1, self.exists_one(l2, l1.port == l2.port && l1.protocol + == l2.protocol && (has(l1.hostname) && has(l2.hostname) ? l1.hostname + == l2.hostname : !has(l1.hostname) && !has(l2.hostname))))' + required: + - gatewayClassName + - listeners + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: Status defines the current state of Gateway. + properties: + addresses: + description: |- + Addresses lists the network addresses that have been bound to the + Gateway. + + This list may differ from the addresses provided in the spec under some + conditions: + + * no addresses are specified, all addresses are dynamically assigned + * a combination of specified and dynamic addresses are assigned + * a specified address was unusable (e.g. already in use) + items: + description: GatewayStatusAddress describes a network address that + is bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: |- + Value of the address. The validity of the values will depend + on the type and support by the controller. + + Examples: `1.2.3.4`, `128::1`, `my-ip-address`. + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching + ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + true' + maxItems: 16 + type: array + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: |- + Conditions describe the current conditions of the Gateway. + + Implementations should prefer to express Gateway conditions + using the `GatewayConditionType` and `GatewayConditionReason` + constants so that operators and tools can converge on a common + vocabulary to describe Gateway state. + + Known condition types are: + + * "Accepted" + * "Programmed" + * "Ready" + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port + defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: |- + AttachedRoutes represents the total number of Routes that have been + successfully attached to this Listener. + + Successful attachment of a Route to a Listener is based solely on the + combination of the AllowedRoutes field on the corresponding Listener + and the Route's ParentRefs field. A Route is successfully attached to + a Listener when it is selected by the Listener's AllowedRoutes field + AND the Route has a valid ParentRef selecting the whole Gateway + resource or a specific Listener as a parent resource (more detail on + attachment semantics can be found in the documentation on the various + Route kinds ParentRefs fields). Listener or Route status does not impact + successful attachment, i.e. the AttachedRoutes field count MUST be set + for Listeners with condition Accepted: false and MUST count successfully + attached Routes that may themselves have Accepted: false conditions. + + Uses for this field include troubleshooting Route attachment and + measuring blast radius/impact of changes to a Listener. + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this + listener. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status + corresponds to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + supportedKinds: + description: |- + SupportedKinds is the list indicating the Kinds supported by this + listener. This MUST represent the kinds an implementation supports for + that Listener configuration. + + If kinds are specified in Spec that are not supported, they MUST NOT + appear in this list and an implementation MUST set the "ResolvedRefs" + condition to "False" with the "InvalidRouteKinds" reason. If both valid + and invalid Route kinds are specified, the implementation MUST + reference the valid Route kinds that have been specified. + items: + description: RouteGroupKind indicates the group and kind of + a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_grpcroutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: grpcroutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: GRPCRoute + listKind: GRPCRouteList + plural: grpcroutes + singular: grpcroute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + GRPCRoute provides a way to route gRPC requests. This includes the capability + to match requests by hostname, gRPC service, gRPC method, or HTTP/2 header. + Filters can be used to specify additional processing steps. Backends specify + where matching requests will be routed. + + GRPCRoute falls under extended support within the Gateway API. Within the + following specification, the word "MUST" indicates that an implementation + supporting GRPCRoute must conform to the indicated requirement, but an + implementation not supporting this route type need not follow the requirement + unless explicitly indicated. + + Implementations supporting `GRPCRoute` with the `HTTPS` `ProtocolType` MUST + accept HTTP/2 connections without an initial upgrade from HTTP/1.1, i.e. via + ALPN. If the implementation does not support this, then it MUST set the + "Accepted" condition to "False" for the affected listener with a reason of + "UnsupportedProtocol". Implementations MAY also accept HTTP/2 connections + with an upgrade from HTTP/1. + + Implementations supporting `GRPCRoute` with the `HTTP` `ProtocolType` MUST + support HTTP/2 over cleartext TCP (h2c, + https://www.rfc-editor.org/rfc/rfc7540#section-3.1) without an initial + upgrade from HTTP/1.1, i.e. with prior knowledge + (https://www.rfc-editor.org/rfc/rfc7540#section-3.4). If the implementation + does not support this, then it MUST set the "Accepted" condition to "False" + for the affected listener with a reason of "UnsupportedProtocol". + Implementations MAY also accept HTTP/2 connections with an upgrade from + HTTP/1, i.e. without prior knowledge. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GRPCRoute. + properties: + hostnames: + description: |- + Hostnames defines a set of hostnames to match against the GRPC + Host header to select a GRPCRoute to process the request. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label MUST appear by itself as the first label. + + If a hostname is specified by both the Listener and GRPCRoute, there + MUST be at least one intersecting hostname for the GRPCRoute to be + attached to the Listener. For example: + + * A Listener with `test.example.com` as the hostname matches GRPCRoutes + that have either not specified any hostnames, or have specified at + least one of `test.example.com` or `*.example.com`. + * A Listener with `*.example.com` as the hostname matches GRPCRoutes + that have either not specified any hostnames or have specified at least + one hostname that matches the Listener hostname. For example, + `test.example.com` and `*.example.com` would both match. On the other + hand, `example.com` and `test.example.net` would not match. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + + If both the Listener and GRPCRoute have specified hostnames, any + GRPCRoute hostnames that do not match the Listener hostname MUST be + ignored. For example, if a Listener specified `*.example.com`, and the + GRPCRoute specified `test.example.com` and `test.example.net`, + `test.example.net` MUST NOT be considered for a match. + + If both the Listener and GRPCRoute have specified hostnames, and none + match with the criteria above, then the GRPCRoute MUST NOT be accepted by + the implementation. The implementation MUST raise an 'Accepted' Condition + with a status of `False` in the corresponding RouteParentStatus. + + If a Route (A) of type HTTPRoute or GRPCRoute is attached to a + Listener and that listener already has another Route (B) of the other + type attached and the intersection of the hostnames of A and B is + non-empty, then the implementation MUST accept exactly one of these two + routes, determined by the following criteria, in order: + + * The oldest Route based on creation timestamp. + * The Route appearing first in alphabetical order by + "{namespace}/{name}". + + The rejected Route MUST raise an 'Accepted' condition with a status of + 'False' in the corresponding RouteParentStatus. + + Support: Core + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) + rules: + description: Rules are a list of GRPC matchers, filters and actions. + items: + description: |- + GRPCRouteRule defines the semantics for matching a gRPC request based on + conditions (matches), processing it (filters), and forwarding the request to + an API object (backendRefs). + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. + + Failure behavior here depends on how many BackendRefs are specified and + how many are invalid. + + If *all* entries in BackendRefs are invalid, and there are also no filters + specified in this route rule, *all* traffic which matches this rule MUST + receive an `UNAVAILABLE` status. + + See the GRPCBackendRef definition for the rules about what makes a single + GRPCBackendRef invalid. + + When a GRPCBackendRef is invalid, `UNAVAILABLE` statuses MUST be returned for + requests that would have otherwise been routed to an invalid backend. If + multiple backends are specified, and some are invalid, the proportion of + requests that would otherwise have been routed to an invalid backend + MUST receive an `UNAVAILABLE` status. + + For example, if two backends are specified with equal weights, and one is + invalid, 50 percent of traffic MUST receive an `UNAVAILABLE` status. + Implementations may choose how that 50 percent is determined. + + Support: Core for Kubernetes Service + + Support: Implementation-specific for any other resource + + Support for weight: Core + items: + description: |- + GRPCBackendRef defines how a GRPCRoute forwards a gRPC request. + + Note that when a namespace different than the local namespace is specified, a + ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + + When the BackendRef points to a Kubernetes Service, implementations SHOULD + honor the appProtocol field if it is set for the target Service Port. + + Implementations supporting appProtocol SHOULD recognize the Kubernetes + Standard Application Protocols defined in KEP-3726. + + If a Service appProtocol isn't specified, an implementation MAY infer the + backend protocol through its own means. Implementations MAY infer the + protocol from the Route type referring to the backend Service. + + If a Route is not able to send traffic to the backend using the specified + protocol then the backend is considered invalid. Implementations MUST set the + "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. + properties: + filters: + description: |- + Filters defined at this level MUST be executed if and only if the + request is being forwarded to the backend defined here. + + Support: Implementation-specific (For broader support of filters, use the + Filters field in GRPCRouteRule.) + items: + description: |- + GRPCRouteFilter defines processing steps that must be completed during the + request or response lifecycle. GRPCRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + Support: Implementation-specific + + This filter can be used multiple times within the same rule. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal + to denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be + specified in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations supporting GRPCRoute MUST support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` MUST be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + enum: + - ResponseHeaderModifier + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil + if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type + != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type + == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil + if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type + != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for + RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == + ''RequestMirror'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for + ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + filters: + description: |- + Filters define the filters that are applied to requests that match + this rule. + + The effects of ordering of multiple behaviors are currently unspecified. + This can change in the future based on feedback during the alpha stage. + + Conformance-levels at this level are defined based on the type of filter: + + - ALL core filters MUST be supported by all implementations that support + GRPCRoute. + - Implementers are encouraged to support extended filters. + - Implementation-specific custom filters have no API guarantees across + implementations. + + Specifying the same filter multiple times is not supported unless explicitly + indicated in the filter. + + If an implementation cannot support a combination of filters, it must clearly + document that limitation. In cases where incompatible or unsupported + filters are specified and cause the `Accepted` condition to be set to status + `False`, implementations may use the `IncompatibleFilters` reason to specify + this configuration error. + + Support: Core + items: + description: |- + GRPCRouteFilter defines processing steps that must be completed during the + request or response lifecycle. GRPCRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + Support: Implementation-specific + + This filter can be used multiple times within the same rule. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal to + denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be specified + in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations supporting GRPCRoute MUST support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` MUST be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + enum: + - ResponseHeaderModifier + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the + filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != + ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == + ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the + filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != + ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror + filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef + filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + matches: + description: |- + Matches define conditions used for matching the rule against incoming + gRPC requests. Each match is independent, i.e. this rule will be matched + if **any** one of the matches is satisfied. + + For example, take the following matches configuration: + + ``` + matches: + - method: + service: foo.bar + headers: + values: + version: 2 + - method: + service: foo.bar.v2 + ``` + + For a request to match against this rule, it MUST satisfy + EITHER of the two conditions: + + - service of foo.bar AND contains the header `version: 2` + - service of foo.bar.v2 + + See the documentation for GRPCRouteMatch on how to specify multiple + match conditions to be ANDed together. + + If no matches are specified, the implementation MUST match every gRPC request. + + Proxy or Load Balancer routing configuration generated from GRPCRoutes + MUST prioritize rules based on the following criteria, continuing on + ties. Merging MUST not be done between GRPCRoutes and HTTPRoutes. + Precedence MUST be given to the rule with the largest number of: + + * Characters in a matching non-wildcard hostname. + * Characters in a matching hostname. + * Characters in a matching service. + * Characters in a matching method. + * Header matches. + + If ties still exist across multiple Routes, matching precedence MUST be + determined in order of the following criteria, continuing on ties: + + * The oldest Route based on creation timestamp. + * The Route appearing first in alphabetical order by + "{namespace}/{name}". + + If ties still exist within the Route that has been given precedence, + matching precedence MUST be granted to the first matching rule meeting + the above criteria. + items: + description: |- + GRPCRouteMatch defines the predicate used to match requests to a given + action. Multiple match types are ANDed together, i.e. the match will + evaluate to true only if all conditions are satisfied. + + For example, the match below will match a gRPC request only if its service + is `foo` AND it contains the `version: v1` header: + + ``` + matches: + - method: + type: Exact + service: "foo" + headers: + - name: "version" + value "v1" + + ``` + properties: + headers: + description: |- + Headers specifies gRPC request header matchers. Multiple match values are + ANDed together, meaning, a request MUST match all the specified headers + to select the route. + items: + description: |- + GRPCHeaderMatch describes how to select a gRPC route by matching gRPC request + headers. + properties: + name: + description: |- + Name is the name of the gRPC Header to be matched. + + If multiple entries specify equivalent header names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: Type specifies how to match against + the value of the header. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of the gRPC Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: |- + Method specifies a gRPC request service/method matcher. If this field is + not specified, all services and methods will match. + properties: + method: + description: |- + Value of the method to match against. If left empty or omitted, will + match all services. + + At least one of Service and Method MUST be a non-empty string. + maxLength: 1024 + type: string + service: + description: |- + Value of the service to match against. If left empty or omitted, will + match any service. + + At least one of Service and Method MUST be a non-empty string. + maxLength: 1024 + type: string + type: + default: Exact + description: |- + Type specifies how to match against the service and/or method. + Support: Core (Exact with service and method specified) + + Support: Implementation-specific (Exact with method specified but no service specified) + + Support: Implementation-specific (RegularExpression) + enum: + - Exact + - RegularExpression + type: string + type: object + x-kubernetes-validations: + - message: One or both of 'service' or 'method' must be + specified + rule: 'has(self.type) ? has(self.service) || has(self.method) + : true' + - message: service must only contain valid characters + (matching ^(?i)\.?[a-z_][a-z_0-9]*(\.[a-z_][a-z_0-9]*)*$) + rule: '(!has(self.type) || self.type == ''Exact'') && + has(self.service) ? self.service.matches(r"""^(?i)\.?[a-z_][a-z_0-9]*(\.[a-z_][a-z_0-9]*)*$"""): + true' + - message: method must only contain valid characters (matching + ^[A-Za-z_][A-Za-z_0-9]*$) + rule: '(!has(self.type) || self.type == ''Exact'') && + has(self.method) ? self.method.matches(r"""^[A-Za-z_][A-Za-z_0-9]*$"""): + true' + type: object + maxItems: 64 + type: array + name: + description: |- + Name is the name of the route rule. This name MUST be unique within a Route if it is set. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + sessionPersistence: + description: |- + SessionPersistence defines and configures session persistence + for the route rule. + + Support: Extended + properties: + absoluteTimeout: + description: |- + AbsoluteTimeout defines the absolute timeout of the persistent + session. Once the AbsoluteTimeout duration has elapsed, the + session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + cookieConfig: + description: |- + CookieConfig provides configuration settings that are specific + to cookie-based session persistence. + + Support: Core + properties: + lifetimeType: + default: Session + description: |- + LifetimeType specifies whether the cookie has a permanent or + session-based lifetime. A permanent cookie persists until its + specified expiry time, defined by the Expires or Max-Age cookie + attributes, while a session cookie is deleted when the current + session ends. + + When set to "Permanent", AbsoluteTimeout indicates the + cookie's lifetime via the Expires or Max-Age cookie attributes + and is required. + + When set to "Session", AbsoluteTimeout indicates the + absolute lifetime of the cookie tracked by the gateway and + is optional. + + Defaults to "Session". + + Support: Core for "Session" type + + Support: Extended for "Permanent" type + enum: + - Permanent + - Session + type: string + type: object + idleTimeout: + description: |- + IdleTimeout defines the idle timeout of the persistent session. + Once the session has been idle for more than the specified + IdleTimeout duration, the session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + sessionName: + description: |- + SessionName defines the name of the persistent session token + which may be reflected in the cookie or the header. Users + should avoid reusing session names to prevent unintended + consequences, such as rejection or unpredictable behavior. + + Support: Implementation-specific + maxLength: 128 + type: string + type: + default: Cookie + description: |- + Type defines the type of session persistence such as through + the use a header or cookie. Defaults to cookie based session + persistence. + + Support: Core for "Cookie" type + + Support: Extended for "Header" type + enum: + - Cookie + - Header + type: string + type: object + x-kubernetes-validations: + - message: AbsoluteTimeout must be specified when cookie lifetimeType + is Permanent + rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType) + || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)' + type: object + maxItems: 16 + type: array + x-kubernetes-validations: + - message: While 16 rules and 64 matches per rule are allowed, the + total number of matches across all rules in a route must be less + than 128 + rule: '(self.size() > 0 ? (has(self[0].matches) ? self[0].matches.size() + : 0) : 0) + (self.size() > 1 ? (has(self[1].matches) ? self[1].matches.size() + : 0) : 0) + (self.size() > 2 ? (has(self[2].matches) ? self[2].matches.size() + : 0) : 0) + (self.size() > 3 ? (has(self[3].matches) ? self[3].matches.size() + : 0) : 0) + (self.size() > 4 ? (has(self[4].matches) ? self[4].matches.size() + : 0) : 0) + (self.size() > 5 ? (has(self[5].matches) ? self[5].matches.size() + : 0) : 0) + (self.size() > 6 ? (has(self[6].matches) ? self[6].matches.size() + : 0) : 0) + (self.size() > 7 ? (has(self[7].matches) ? self[7].matches.size() + : 0) : 0) + (self.size() > 8 ? (has(self[8].matches) ? self[8].matches.size() + : 0) : 0) + (self.size() > 9 ? (has(self[9].matches) ? self[9].matches.size() + : 0) : 0) + (self.size() > 10 ? (has(self[10].matches) ? self[10].matches.size() + : 0) : 0) + (self.size() > 11 ? (has(self[11].matches) ? self[11].matches.size() + : 0) : 0) + (self.size() > 12 ? (has(self[12].matches) ? self[12].matches.size() + : 0) : 0) + (self.size() > 13 ? (has(self[13].matches) ? self[13].matches.size() + : 0) : 0) + (self.size() > 14 ? (has(self[14].matches) ? self[14].matches.size() + : 0) : 0) + (self.size() > 15 ? (has(self[15].matches) ? self[15].matches.size() + : 0) : 0) <= 128' + - message: Rule name must be unique within the route + rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) + && l1.name == l2.name)) + type: object + status: + description: Status defines the current state of GRPCRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_httproutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: httproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: HTTPRoute + listKind: HTTPRouteList + plural: httproutes + singular: httproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + HTTPRoute provides a way to route HTTP requests. This includes the capability + to match requests by hostname, path, header, or query param. Filters can be + used to specify additional processing steps. Backends specify where matching + requests should be routed. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of HTTPRoute. + properties: + hostnames: + description: |- + Hostnames defines a set of hostnames that should match against the HTTP Host + header to select a HTTPRoute used to process the request. Implementations + MUST ignore any port value specified in the HTTP Host header while + performing a match and (absent of any applicable header modification + configuration) MUST forward this header unmodified to the backend. + + Valid values for Hostnames are determined by RFC 1123 definition of a + hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + If a hostname is specified by both the Listener and HTTPRoute, there + must be at least one intersecting hostname for the HTTPRoute to be + attached to the Listener. For example: + + * A Listener with `test.example.com` as the hostname matches HTTPRoutes + that have either not specified any hostnames, or have specified at + least one of `test.example.com` or `*.example.com`. + * A Listener with `*.example.com` as the hostname matches HTTPRoutes + that have either not specified any hostnames or have specified at least + one hostname that matches the Listener hostname. For example, + `*.example.com`, `test.example.com`, and `foo.test.example.com` would + all match. On the other hand, `example.com` and `test.example.net` would + not match. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + + If both the Listener and HTTPRoute have specified hostnames, any + HTTPRoute hostnames that do not match the Listener hostname MUST be + ignored. For example, if a Listener specified `*.example.com`, and the + HTTPRoute specified `test.example.com` and `test.example.net`, + `test.example.net` must not be considered for a match. + + If both the Listener and HTTPRoute have specified hostnames, and none + match with the criteria above, then the HTTPRoute is not accepted. The + implementation must raise an 'Accepted' Condition with a status of + `False` in the corresponding RouteParentStatus. + + In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. + overlapping wildcard matching and exact matching hostnames), precedence must + be given to rules from the HTTPRoute with the largest number of: + + * Characters in a matching non-wildcard hostname. + * Characters in a matching hostname. + + If ties exist across multiple Routes, the matching precedence rules for + HTTPRouteMatches takes over. + + Support: Core + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) + rules: + default: + - matches: + - path: + type: PathPrefix + value: / + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: |- + HTTPRouteRule defines semantics for matching an HTTP request based on + conditions (matches), processing it (filters), and forwarding the request to + an API object (backendRefs). + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. + + Failure behavior here depends on how many BackendRefs are specified and + how many are invalid. + + If *all* entries in BackendRefs are invalid, and there are also no filters + specified in this route rule, *all* traffic which matches this rule MUST + receive a 500 status code. + + See the HTTPBackendRef definition for the rules about what makes a single + HTTPBackendRef invalid. + + When a HTTPBackendRef is invalid, 500 status codes MUST be returned for + requests that would have otherwise been routed to an invalid backend. If + multiple backends are specified, and some are invalid, the proportion of + requests that would otherwise have been routed to an invalid backend + MUST receive a 500 status code. + + For example, if two backends are specified with equal weights, and one is + invalid, 50 percent of traffic must receive a 500. Implementations may + choose how that 50 percent is determined. + + When a HTTPBackendRef refers to a Service that has no ready endpoints, + implementations SHOULD return a 503 for requests to that backend instead. + If an implementation chooses to do this, all of the above rules for 500 responses + MUST also apply for responses that return a 503. + + Support: Core for Kubernetes Service + + Support: Extended for Kubernetes ServiceImport + + Support: Implementation-specific for any other resource + + Support for weight: Core + items: + description: |- + HTTPBackendRef defines how a HTTPRoute forwards a HTTP request. + + Note that when a namespace different than the local namespace is specified, a + ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + + When the BackendRef points to a Kubernetes Service, implementations SHOULD + honor the appProtocol field if it is set for the target Service Port. + + Implementations supporting appProtocol SHOULD recognize the Kubernetes + Standard Application Protocols defined in KEP-3726. + + If a Service appProtocol isn't specified, an implementation MAY infer the + backend protocol through its own means. Implementations MAY infer the + protocol from the Route type referring to the backend Service. + + If a Route is not able to send traffic to the backend using the specified + protocol then the backend is considered invalid. Implementations MUST set the + "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. + properties: + filters: + description: |- + Filters defined at this level should be executed if and only if the + request is being forwarded to the backend defined here. + + Support: Implementation-specific (For broader support of filters, use the + Filters field in HTTPRouteRule.) + items: + description: |- + HTTPRouteFilter defines processing steps that must be completed during the + request or response lifecycle. HTTPRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + cors: + description: |- + CORS defines a schema for a filter that responds to the + cross-origin request based on HTTP response header. + + Support: Extended + properties: + allowCredentials: + description: |- + AllowCredentials indicates whether the actual cross-origin request allows + to include credentials. + + The only valid value for the `Access-Control-Allow-Credentials` response + header is true (case-sensitive). + + If the credentials are not allowed in cross-origin requests, the gateway + will omit the header `Access-Control-Allow-Credentials` entirely rather + than setting its value to false. + + Support: Extended + enum: + - true + type: boolean + allowHeaders: + description: |- + AllowHeaders indicates which HTTP request headers are supported for + accessing the requested resource. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Allow-Headers` + response header are separated by a comma (","). + + When the `AllowHeaders` field is configured with one or more headers, the + gateway must return the `Access-Control-Allow-Headers` response header + which value is present in the `AllowHeaders` field. + + If any header name in the `Access-Control-Request-Headers` request header + is not included in the list of header names specified by the response + header `Access-Control-Allow-Headers`, it will present an error on the + client side. + + If any header name in the `Access-Control-Allow-Headers` response header + does not recognize by the client, it will also occur an error on the + client side. + + A wildcard indicates that the requests with all HTTP headers are allowed. + The `Access-Control-Allow-Headers` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowHeaders` field + specified with the `*` wildcard, the gateway must specify one or more + HTTP headers in the value of the `Access-Control-Allow-Headers` response + header. The value of the header `Access-Control-Allow-Headers` is same as + the `Access-Control-Request-Headers` header provided by the client. If + the header `Access-Control-Request-Headers` is not included in the + request, the gateway will omit the `Access-Control-Allow-Headers` + response header, instead of specifying the `*` wildcard. A Gateway + implementation may choose to add implementation-specific default headers. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + allowMethods: + description: |- + AllowMethods indicates which HTTP methods are supported for accessing the + requested resource. + + Valid values are any method defined by RFC9110, along with the special + value `*`, which represents all HTTP methods are allowed. + + Method names are case sensitive, so these values are also case-sensitive. + (See https://www.rfc-editor.org/rfc/rfc2616#section-5.1.1) + + Multiple method names in the value of the `Access-Control-Allow-Methods` + response header are separated by a comma (","). + + A CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`. + (See https://fetch.spec.whatwg.org/#cors-safelisted-method) The + CORS-safelisted methods are always allowed, regardless of whether they + are specified in the `AllowMethods` field. + + When the `AllowMethods` field is configured with one or more methods, the + gateway must return the `Access-Control-Allow-Methods` response header + which value is present in the `AllowMethods` field. + + If the HTTP method of the `Access-Control-Request-Method` request header + is not included in the list of methods specified by the response header + `Access-Control-Allow-Methods`, it will present an error on the client + side. + + The `Access-Control-Allow-Methods` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowMethods` field + specified with the `*` wildcard, the gateway must specify one HTTP method + in the value of the Access-Control-Allow-Methods response header. The + value of the header `Access-Control-Allow-Methods` is same as the + `Access-Control-Request-Method` header provided by the client. If the + header `Access-Control-Request-Method` is not included in the request, + the gateway will omit the `Access-Control-Allow-Methods` response header, + instead of specifying the `*` wildcard. A Gateway implementation may + choose to add implementation-specific default methods. + + Support: Extended + items: + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + - '*' + type: string + maxItems: 9 + type: array + x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowMethods cannot contain '*' alongside + other methods + rule: '!(''*'' in self && self.size() > 1)' + allowOrigins: + description: |- + AllowOrigins indicates whether the response can be shared with requested + resource from the given `Origin`. + + The `Origin` consists of a scheme and a host, with an optional port, and + takes the form `://(:)`. + + Valid values for scheme are: `http` and `https`. + + Valid values for port are any integer between 1 and 65535 (the list of + available TCP/UDP ports). Note that, if not included, port `80` is + assumed for `http` scheme origins, and port `443` is assumed for `https` + origins. This may affect origin matching. + + The host part of the origin may contain the wildcard character `*`. These + wildcard characters behave as follows: + + * `*` is a greedy match to the _left_, including any number of + DNS labels to the left of its position. This also means that + `*` will include any number of period `.` characters to the + left of its position. + * A wildcard by itself matches all hosts. + + An origin value that includes _only_ the `*` character indicates requests + from all `Origin`s are allowed. + + When the `AllowOrigins` field is configured with multiple origins, it + means the server supports clients from multiple origins. If the request + `Origin` matches the configured allowed origins, the gateway must return + the given `Origin` and sets value of the header + `Access-Control-Allow-Origin` same as the `Origin` header provided by the + client. + + The status code of a successful response to a "preflight" request is + always an OK status (i.e., 204 or 200). + + If the request `Origin` does not match the configured allowed origins, + the gateway returns 204/200 response but doesn't set the relevant + cross-origin response headers. Alternatively, the gateway responds with + 403 status to the "preflight" request is denied, coupled with omitting + the CORS headers. The cross-origin request fails on the client side. + Therefore, the client doesn't attempt the actual cross-origin request. + + The `Access-Control-Allow-Origin` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowOrigins` field + specified with the `*` wildcard, the gateway must return a single origin + in the value of the `Access-Control-Allow-Origin` response header, + instead of specifying the `*` wildcard. The value of the header + `Access-Control-Allow-Origin` is same as the `Origin` header provided by + the client. + + Support: Extended + items: + description: |- + The AbsoluteURI MUST NOT be a relative URI, and it MUST follow the URI syntax and + encoding rules specified in RFC3986. The AbsoluteURI MUST include both a + scheme (e.g., "http" or "spiffe") and a scheme-specific-part. URIs that + include an authority MUST include a fully qualified domain name or + IP address as the host. + maxLength: 253 + minLength: 1 + pattern: ^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))? + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + exposeHeaders: + description: |- + ExposeHeaders indicates which HTTP response headers can be exposed + to client-side scripts in response to a cross-origin request. + + A CORS-safelisted response header is an HTTP header in a CORS response + that it is considered safe to expose to the client scripts. + The CORS-safelisted response headers include the following headers: + `Cache-Control` + `Content-Language` + `Content-Length` + `Content-Type` + `Expires` + `Last-Modified` + `Pragma` + (See https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name) + The CORS-safelisted response headers are exposed to client by default. + + When an HTTP header name is specified using the `ExposeHeaders` field, + this additional header will be exposed as part of the response to the + client. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Expose-Headers` + response header are separated by a comma (","). + + A wildcard indicates that the responses with all HTTP headers are exposed + to clients. The `Access-Control-Expose-Headers` response header can only + use `*` wildcard as value when the `AllowCredentials` field is + unspecified. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + maxAge: + default: 5 + description: |- + MaxAge indicates the duration (in seconds) for the client to cache the + results of a "preflight" request. + + The information provided by the `Access-Control-Allow-Methods` and + `Access-Control-Allow-Headers` response headers can be cached by the + client until the time specified by `Access-Control-Max-Age` elapses. + + The default value of `Access-Control-Max-Age` response header is 5 + (seconds). + format: int32 + minimum: 1 + type: integer + type: object + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + This filter can be used multiple times within the same rule. + + Support: Implementation-specific + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal + to denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be + specified in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + requestRedirect: + description: |- + RequestRedirect defines a schema for a filter that responds to the + request with an HTTP redirection. + + Support: Core + properties: + hostname: + description: |- + Hostname is the hostname to be used in the value of the `Location` + header in the response. + When empty, the hostname in the `Host` header of the request is used. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines parameters used to modify the path of the incoming request. + The modified path is then used to construct the `Location` header. When + empty, the request path is used as-is. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified + when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? + has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when + replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type + == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified + when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' + ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' + when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + port: + description: |- + Port is the port to be used in the value of the `Location` + header in the response. + + If no port is specified, the redirect port MUST be derived using the + following rules: + + * If redirect scheme is not-empty, the redirect port MUST be the well-known + port associated with the redirect scheme. Specifically "http" to port 80 + and "https" to port 443. If the redirect scheme does not have a + well-known port, the listener port of the Gateway SHOULD be used. + * If redirect scheme is empty, the redirect port MUST be the Gateway + Listener port. + + Implementations SHOULD NOT add the port number in the 'Location' + header in the following cases: + + * A Location header that will use HTTP (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 80. + * A Location header that will use HTTPS (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 443. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: |- + Scheme is the scheme to be used in the value of the `Location` header in + the response. When empty, the scheme of the request is used. + + Scheme redirects can affect the port of the redirect, for more information, + refer to the documentation for the port field of this filter. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Extended + enum: + - http + - https + type: string + statusCode: + default: 302 + description: |- + StatusCode is the HTTP status code to be used in response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Core + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations must support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by + specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` should be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + - CORS + type: string + urlRewrite: + description: |- + URLRewrite defines a schema for a filter that modifies a request during forwarding. + + Support: Extended + properties: + hostname: + description: |- + Hostname is the value to be used to replace the Host header value during + forwarding. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines a path rewrite. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified + when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? + has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when + replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type + == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified + when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' + ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' + when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil + if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type + != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type + == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil + if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type + != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for + RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == + ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the + filter.type is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != + ''RequestRedirect'')' + - message: filter.requestRedirect must be specified + for RequestRedirect filter.type + rule: '!(!has(self.requestRedirect) && self.type == + ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type + is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite + filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for + ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + - message: filter.cors must be nil if the filter.type + is not CORS + rule: '!(has(self.cors) && self.type != ''CORS'')' + - message: filter.cors must be specified for CORS filter.type + rule: '!(!has(self.cors) && self.type == ''CORS'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') + && self.exists(f, f.type == ''URLRewrite''))' + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') + && self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() + <= 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() + <= 1 + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + filters: + description: |- + Filters define the filters that are applied to requests that match + this rule. + + Wherever possible, implementations SHOULD implement filters in the order + they are specified. + + Implementations MAY choose to implement this ordering strictly, rejecting + any combination or order of filters that cannot be supported. If implementations + choose a strict interpretation of filter ordering, they MUST clearly document + that behavior. + + To reject an invalid combination or order of filters, implementations SHOULD + consider the Route Rules with this configuration invalid. If all Route Rules + in a Route are invalid, the entire Route would be considered invalid. If only + a portion of Route Rules are invalid, implementations MUST set the + "PartiallyInvalid" condition for the Route. + + Conformance-levels at this level are defined based on the type of filter: + + - ALL core filters MUST be supported by all implementations. + - Implementers are encouraged to support extended filters. + - Implementation-specific custom filters have no API guarantees across + implementations. + + Specifying the same filter multiple times is not supported unless explicitly + indicated in the filter. + + All filters are expected to be compatible with each other except for the + URLRewrite and RequestRedirect filters, which may not be combined. If an + implementation cannot support other combinations of filters, they must clearly + document that limitation. In cases where incompatible or unsupported + filters are specified and cause the `Accepted` condition to be set to status + `False`, implementations may use the `IncompatibleFilters` reason to specify + this configuration error. + + Support: Core + items: + description: |- + HTTPRouteFilter defines processing steps that must be completed during the + request or response lifecycle. HTTPRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + cors: + description: |- + CORS defines a schema for a filter that responds to the + cross-origin request based on HTTP response header. + + Support: Extended + properties: + allowCredentials: + description: |- + AllowCredentials indicates whether the actual cross-origin request allows + to include credentials. + + The only valid value for the `Access-Control-Allow-Credentials` response + header is true (case-sensitive). + + If the credentials are not allowed in cross-origin requests, the gateway + will omit the header `Access-Control-Allow-Credentials` entirely rather + than setting its value to false. + + Support: Extended + enum: + - true + type: boolean + allowHeaders: + description: |- + AllowHeaders indicates which HTTP request headers are supported for + accessing the requested resource. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Allow-Headers` + response header are separated by a comma (","). + + When the `AllowHeaders` field is configured with one or more headers, the + gateway must return the `Access-Control-Allow-Headers` response header + which value is present in the `AllowHeaders` field. + + If any header name in the `Access-Control-Request-Headers` request header + is not included in the list of header names specified by the response + header `Access-Control-Allow-Headers`, it will present an error on the + client side. + + If any header name in the `Access-Control-Allow-Headers` response header + does not recognize by the client, it will also occur an error on the + client side. + + A wildcard indicates that the requests with all HTTP headers are allowed. + The `Access-Control-Allow-Headers` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowHeaders` field + specified with the `*` wildcard, the gateway must specify one or more + HTTP headers in the value of the `Access-Control-Allow-Headers` response + header. The value of the header `Access-Control-Allow-Headers` is same as + the `Access-Control-Request-Headers` header provided by the client. If + the header `Access-Control-Request-Headers` is not included in the + request, the gateway will omit the `Access-Control-Allow-Headers` + response header, instead of specifying the `*` wildcard. A Gateway + implementation may choose to add implementation-specific default headers. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + allowMethods: + description: |- + AllowMethods indicates which HTTP methods are supported for accessing the + requested resource. + + Valid values are any method defined by RFC9110, along with the special + value `*`, which represents all HTTP methods are allowed. + + Method names are case sensitive, so these values are also case-sensitive. + (See https://www.rfc-editor.org/rfc/rfc2616#section-5.1.1) + + Multiple method names in the value of the `Access-Control-Allow-Methods` + response header are separated by a comma (","). + + A CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`. + (See https://fetch.spec.whatwg.org/#cors-safelisted-method) The + CORS-safelisted methods are always allowed, regardless of whether they + are specified in the `AllowMethods` field. + + When the `AllowMethods` field is configured with one or more methods, the + gateway must return the `Access-Control-Allow-Methods` response header + which value is present in the `AllowMethods` field. + + If the HTTP method of the `Access-Control-Request-Method` request header + is not included in the list of methods specified by the response header + `Access-Control-Allow-Methods`, it will present an error on the client + side. + + The `Access-Control-Allow-Methods` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowMethods` field + specified with the `*` wildcard, the gateway must specify one HTTP method + in the value of the Access-Control-Allow-Methods response header. The + value of the header `Access-Control-Allow-Methods` is same as the + `Access-Control-Request-Method` header provided by the client. If the + header `Access-Control-Request-Method` is not included in the request, + the gateway will omit the `Access-Control-Allow-Methods` response header, + instead of specifying the `*` wildcard. A Gateway implementation may + choose to add implementation-specific default methods. + + Support: Extended + items: + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + - '*' + type: string + maxItems: 9 + type: array + x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowMethods cannot contain '*' alongside + other methods + rule: '!(''*'' in self && self.size() > 1)' + allowOrigins: + description: |- + AllowOrigins indicates whether the response can be shared with requested + resource from the given `Origin`. + + The `Origin` consists of a scheme and a host, with an optional port, and + takes the form `://(:)`. + + Valid values for scheme are: `http` and `https`. + + Valid values for port are any integer between 1 and 65535 (the list of + available TCP/UDP ports). Note that, if not included, port `80` is + assumed for `http` scheme origins, and port `443` is assumed for `https` + origins. This may affect origin matching. + + The host part of the origin may contain the wildcard character `*`. These + wildcard characters behave as follows: + + * `*` is a greedy match to the _left_, including any number of + DNS labels to the left of its position. This also means that + `*` will include any number of period `.` characters to the + left of its position. + * A wildcard by itself matches all hosts. + + An origin value that includes _only_ the `*` character indicates requests + from all `Origin`s are allowed. + + When the `AllowOrigins` field is configured with multiple origins, it + means the server supports clients from multiple origins. If the request + `Origin` matches the configured allowed origins, the gateway must return + the given `Origin` and sets value of the header + `Access-Control-Allow-Origin` same as the `Origin` header provided by the + client. + + The status code of a successful response to a "preflight" request is + always an OK status (i.e., 204 or 200). + + If the request `Origin` does not match the configured allowed origins, + the gateway returns 204/200 response but doesn't set the relevant + cross-origin response headers. Alternatively, the gateway responds with + 403 status to the "preflight" request is denied, coupled with omitting + the CORS headers. The cross-origin request fails on the client side. + Therefore, the client doesn't attempt the actual cross-origin request. + + The `Access-Control-Allow-Origin` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowOrigins` field + specified with the `*` wildcard, the gateway must return a single origin + in the value of the `Access-Control-Allow-Origin` response header, + instead of specifying the `*` wildcard. The value of the header + `Access-Control-Allow-Origin` is same as the `Origin` header provided by + the client. + + Support: Extended + items: + description: |- + The AbsoluteURI MUST NOT be a relative URI, and it MUST follow the URI syntax and + encoding rules specified in RFC3986. The AbsoluteURI MUST include both a + scheme (e.g., "http" or "spiffe") and a scheme-specific-part. URIs that + include an authority MUST include a fully qualified domain name or + IP address as the host. + maxLength: 253 + minLength: 1 + pattern: ^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))? + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + exposeHeaders: + description: |- + ExposeHeaders indicates which HTTP response headers can be exposed + to client-side scripts in response to a cross-origin request. + + A CORS-safelisted response header is an HTTP header in a CORS response + that it is considered safe to expose to the client scripts. + The CORS-safelisted response headers include the following headers: + `Cache-Control` + `Content-Language` + `Content-Length` + `Content-Type` + `Expires` + `Last-Modified` + `Pragma` + (See https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name) + The CORS-safelisted response headers are exposed to client by default. + + When an HTTP header name is specified using the `ExposeHeaders` field, + this additional header will be exposed as part of the response to the + client. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Expose-Headers` + response header are separated by a comma (","). + + A wildcard indicates that the responses with all HTTP headers are exposed + to clients. The `Access-Control-Expose-Headers` response header can only + use `*` wildcard as value when the `AllowCredentials` field is + unspecified. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + maxAge: + default: 5 + description: |- + MaxAge indicates the duration (in seconds) for the client to cache the + results of a "preflight" request. + + The information provided by the `Access-Control-Allow-Methods` and + `Access-Control-Allow-Headers` response headers can be cached by the + client until the time specified by `Access-Control-Max-Age` elapses. + + The default value of `Access-Control-Max-Age` response header is 5 + (seconds). + format: int32 + minimum: 1 + type: integer + type: object + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + This filter can be used multiple times within the same rule. + + Support: Implementation-specific + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal to + denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be specified + in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + requestRedirect: + description: |- + RequestRedirect defines a schema for a filter that responds to the + request with an HTTP redirection. + + Support: Core + properties: + hostname: + description: |- + Hostname is the hostname to be used in the value of the `Location` + header in the response. + When empty, the hostname in the `Host` header of the request is used. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines parameters used to modify the path of the incoming request. + The modified path is then used to construct the `Location` header. When + empty, the request path is used as-is. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when + type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) + : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath + is set + rule: 'has(self.replaceFullPath) ? self.type == + ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when + type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) + : true' + - message: type must be 'ReplacePrefixMatch' when + replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + port: + description: |- + Port is the port to be used in the value of the `Location` + header in the response. + + If no port is specified, the redirect port MUST be derived using the + following rules: + + * If redirect scheme is not-empty, the redirect port MUST be the well-known + port associated with the redirect scheme. Specifically "http" to port 80 + and "https" to port 443. If the redirect scheme does not have a + well-known port, the listener port of the Gateway SHOULD be used. + * If redirect scheme is empty, the redirect port MUST be the Gateway + Listener port. + + Implementations SHOULD NOT add the port number in the 'Location' + header in the following cases: + + * A Location header that will use HTTP (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 80. + * A Location header that will use HTTPS (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 443. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: |- + Scheme is the scheme to be used in the value of the `Location` header in + the response. When empty, the scheme of the request is used. + + Scheme redirects can affect the port of the redirect, for more information, + refer to the documentation for the port field of this filter. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Extended + enum: + - http + - https + type: string + statusCode: + default: 302 + description: |- + StatusCode is the HTTP status code to be used in response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Core + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations must support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by + specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` should be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + - CORS + type: string + urlRewrite: + description: |- + URLRewrite defines a schema for a filter that modifies a request during forwarding. + + Support: Extended + properties: + hostname: + description: |- + Hostname is the value to be used to replace the Host header value during + forwarding. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines a path rewrite. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when + type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) + : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath + is set + rule: 'has(self.replaceFullPath) ? self.type == + ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when + type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) + : true' + - message: type must be 'ReplacePrefixMatch' when + replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the + filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != + ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == + ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the + filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != + ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror + filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the filter.type + is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')' + - message: filter.requestRedirect must be specified for RequestRedirect + filter.type + rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type + is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite + filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef + filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + - message: filter.cors must be nil if the filter.type is not + CORS + rule: '!(has(self.cors) && self.type != ''CORS'')' + - message: filter.cors must be specified for CORS filter.type + rule: '!(!has(self.cors) && self.type == ''CORS'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') && + self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() <= + 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() <= 1 + matches: + default: + - path: + type: PathPrefix + value: / + description: |- + Matches define conditions used for matching the rule against incoming + HTTP requests. Each match is independent, i.e. this rule will be matched + if **any** one of the matches is satisfied. + + For example, take the following matches configuration: + + ``` + matches: + - path: + value: "/foo" + headers: + - name: "version" + value: "v2" + - path: + value: "/v2/foo" + ``` + + For a request to match against this rule, a request must satisfy + EITHER of the two conditions: + + - path prefixed with `/foo` AND contains the header `version: v2` + - path prefix of `/v2/foo` + + See the documentation for HTTPRouteMatch on how to specify multiple + match conditions that should be ANDed together. + + If no matches are specified, the default is a prefix + path match on "/", which has the effect of matching every + HTTP request. + + Proxy or Load Balancer routing configuration generated from HTTPRoutes + MUST prioritize matches based on the following criteria, continuing on + ties. Across all rules specified on applicable Routes, precedence must be + given to the match having: + + * "Exact" path match. + * "Prefix" path match with largest number of characters. + * Method match. + * Largest number of header matches. + * Largest number of query param matches. + + Note: The precedence of RegularExpression path matches are implementation-specific. + + If ties still exist across multiple Routes, matching precedence MUST be + determined in order of the following criteria, continuing on ties: + + * The oldest Route based on creation timestamp. + * The Route appearing first in alphabetical order by + "{namespace}/{name}". + + If ties still exist within an HTTPRoute, matching precedence MUST be granted + to the FIRST matching rule (in list order) with a match meeting the above + criteria. + + When no rules matching a request have been successfully attached to the + parent a request is coming from, a HTTP 404 status code MUST be returned. + items: + description: "HTTPRouteMatch defines the predicate used to + match requests to a given\naction. Multiple match types + are ANDed together, i.e. the match will\nevaluate to true + only if all conditions are satisfied.\n\nFor example, the + match below will match a HTTP request only if its path\nstarts + with `/foo` AND it contains the `version: v1` header:\n\n```\nmatch:\n\n\tpath:\n\t + \ value: \"/foo\"\n\theaders:\n\t- name: \"version\"\n\t + \ value \"v1\"\n\n```" + properties: + headers: + description: |- + Headers specifies HTTP request header matchers. Multiple match values are + ANDed together, meaning, a request must match all the specified headers + to select the route. + items: + description: |- + HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request + headers. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + + When a header is repeated in an HTTP request, it is + implementation-specific behavior as to how this is represented. + Generally, proxies should follow the guidance from the RFC: + https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding + processing a repeated header, with special handling for "Set-Cookie". + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the header. + + Support: Core (Exact) + + Support: Implementation-specific (RegularExpression) + + Since RegularExpression HeaderMatchType has implementation-specific + conformance, implementations can support POSIX, PCRE or any other dialects + of regular expressions. Please read the implementation's documentation to + determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP Header to + be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: |- + Method specifies HTTP method matcher. + When specified, this route will be matched only if the request has the + specified method. + + Support: Extended + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: |- + Path specifies a HTTP request path matcher. If this field is not + specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: |- + Type specifies how to match against the path Value. + + Support: Core (Exact, PathPrefix) + + Support: Implementation-specific (RegularExpression) + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path to match against. + maxLength: 1024 + type: string + type: object + x-kubernetes-validations: + - message: value must be an absolute path and start with + '/' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.startsWith(''/'') + : true' + - message: must not contain '//' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''//'') + : true' + - message: must not contain '/./' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/./'') + : true' + - message: must not contain '/../' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/../'') + : true' + - message: must not contain '%2f' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2f'') + : true' + - message: must not contain '%2F' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2F'') + : true' + - message: must not contain '#' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''#'') + : true' + - message: must not end with '/..' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/..'') + : true' + - message: must not end with '/.' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/.'') + : true' + - message: type must be one of ['Exact', 'PathPrefix', + 'RegularExpression'] + rule: self.type in ['Exact','PathPrefix'] || self.type + == 'RegularExpression' + - message: must only contain valid characters (matching + ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$) + for types ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.matches(r"""^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$""") + : true' + queryParams: + description: |- + QueryParams specifies HTTP query parameter matchers. Multiple match + values are ANDed together, meaning, a request must match all the + specified query parameters to select the route. + + Support: Extended + items: + description: |- + HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP + query parameters. + properties: + name: + description: |- + Name is the name of the HTTP query param to be matched. This must be an + exact string match. (See + https://tools.ietf.org/html/rfc7230#section-2.7.3). + + If multiple entries specify equivalent query param names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent query param name MUST be ignored. + + If a query param is repeated in an HTTP request, the behavior is + purposely left undefined, since different data planes have different + capabilities. However, it is *recommended* that implementations should + match against the first value of the param if the data plane supports it, + as this behavior is expected in other load balancing contexts outside of + the Gateway API. + + Users SHOULD NOT route traffic based on repeated query params to guard + themselves against potential differences in the implementations. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the query parameter. + + Support: Extended (Exact) + + Support: Implementation-specific (RegularExpression) + + Since RegularExpression QueryParamMatchType has Implementation-specific + conformance, implementations can support POSIX, PCRE or any other + dialects of regular expressions. Please read the implementation's + documentation to determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP query param + to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 64 + type: array + name: + description: |- + Name is the name of the route rule. This name MUST be unique within a Route if it is set. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + retry: + description: |- + Retry defines the configuration for when to retry an HTTP request. + + Support: Extended + properties: + attempts: + description: |- + Attempts specifies the maximum number of times an individual request + from the gateway to a backend should be retried. + + If the maximum number of retries has been attempted without a successful + response from the backend, the Gateway MUST return an error. + + When this field is unspecified, the number of times to attempt to retry + a backend request is implementation-specific. + + Support: Extended + type: integer + backoff: + description: |- + Backoff specifies the minimum duration a Gateway should wait between + retry attempts and is represented in Gateway API Duration formatting. + + For example, setting the `rules[].retry.backoff` field to the value + `100ms` will cause a backend request to first be retried approximately + 100 milliseconds after timing out or receiving a response code configured + to be retryable. + + An implementation MAY use an exponential or alternative backoff strategy + for subsequent retry attempts, MAY cap the maximum backoff duration to + some amount greater than the specified minimum, and MAY add arbitrary + jitter to stagger requests, as long as unsuccessful backend requests are + not retried before the configured minimum duration. + + If a Request timeout (`rules[].timeouts.request`) is configured on the + route, the entire duration of the initial request and any retry attempts + MUST not exceed the Request timeout duration. If any retry attempts are + still in progress when the Request timeout duration has been reached, + these SHOULD be canceled if possible and the Gateway MUST immediately + return a timeout error. + + If a BackendRequest timeout (`rules[].timeouts.backendRequest`) is + configured on the route, any retry attempts which reach the configured + BackendRequest timeout duration without a response SHOULD be canceled if + possible and the Gateway should wait for at least the specified backoff + duration before attempting to retry the backend request again. + + If a BackendRequest timeout is _not_ configured on the route, retry + attempts MAY time out after an implementation default duration, or MAY + remain pending until a configured Request timeout or implementation + default duration for total request time is reached. + + When this field is unspecified, the time to wait between retry attempts + is implementation-specific. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + codes: + description: |- + Codes defines the HTTP response status codes for which a backend request + should be retried. + + Support: Extended + items: + description: |- + HTTPRouteRetryStatusCode defines an HTTP response status code for + which a backend request should be retried. + + Implementations MUST support the following status codes as retryable: + + * 500 + * 502 + * 503 + * 504 + + Implementations MAY support specifying additional discrete values in the + 500-599 range. + + Implementations MAY support specifying discrete values in the 400-499 range, + which are often inadvisable to retry. + maximum: 599 + minimum: 400 + type: integer + type: array + type: object + sessionPersistence: + description: |- + SessionPersistence defines and configures session persistence + for the route rule. + + Support: Extended + properties: + absoluteTimeout: + description: |- + AbsoluteTimeout defines the absolute timeout of the persistent + session. Once the AbsoluteTimeout duration has elapsed, the + session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + cookieConfig: + description: |- + CookieConfig provides configuration settings that are specific + to cookie-based session persistence. + + Support: Core + properties: + lifetimeType: + default: Session + description: |- + LifetimeType specifies whether the cookie has a permanent or + session-based lifetime. A permanent cookie persists until its + specified expiry time, defined by the Expires or Max-Age cookie + attributes, while a session cookie is deleted when the current + session ends. + + When set to "Permanent", AbsoluteTimeout indicates the + cookie's lifetime via the Expires or Max-Age cookie attributes + and is required. + + When set to "Session", AbsoluteTimeout indicates the + absolute lifetime of the cookie tracked by the gateway and + is optional. + + Defaults to "Session". + + Support: Core for "Session" type + + Support: Extended for "Permanent" type + enum: + - Permanent + - Session + type: string + type: object + idleTimeout: + description: |- + IdleTimeout defines the idle timeout of the persistent session. + Once the session has been idle for more than the specified + IdleTimeout duration, the session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + sessionName: + description: |- + SessionName defines the name of the persistent session token + which may be reflected in the cookie or the header. Users + should avoid reusing session names to prevent unintended + consequences, such as rejection or unpredictable behavior. + + Support: Implementation-specific + maxLength: 128 + type: string + type: + default: Cookie + description: |- + Type defines the type of session persistence such as through + the use a header or cookie. Defaults to cookie based session + persistence. + + Support: Core for "Cookie" type + + Support: Extended for "Header" type + enum: + - Cookie + - Header + type: string + type: object + x-kubernetes-validations: + - message: AbsoluteTimeout must be specified when cookie lifetimeType + is Permanent + rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType) + || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)' + timeouts: + description: |- + Timeouts defines the timeouts that can be configured for an HTTP request. + + Support: Extended + properties: + backendRequest: + description: |- + BackendRequest specifies a timeout for an individual request from the gateway + to a backend. This covers the time from when the request first starts being + sent from the gateway to when the full response has been received from the backend. + + Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout + completely. Implementations that cannot completely disable the timeout MUST + instead interpret the zero duration as the longest possible value to which + the timeout can be set. + + An entire client HTTP transaction with a gateway, covered by the Request timeout, + may result in more than one call from the gateway to the destination backend, + for example, if automatic retries are supported. + + The value of BackendRequest must be a Gateway API Duration string as defined by + GEP-2257. When this field is unspecified, its behavior is implementation-specific; + when specified, the value of BackendRequest must be no more than the value of the + Request timeout (since the Request timeout encompasses the BackendRequest timeout). + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + request: + description: |- + Request specifies the maximum duration for a gateway to respond to an HTTP request. + If the gateway has not been able to respond before this deadline is met, the gateway + MUST return a timeout error. + + For example, setting the `rules.timeouts.request` field to the value `10s` in an + `HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds + to complete. + + Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout + completely. Implementations that cannot completely disable the timeout MUST + instead interpret the zero duration as the longest possible value to which + the timeout can be set. + + This timeout is intended to cover as close to the whole request-response transaction + as possible although an implementation MAY choose to start the timeout after the entire + request stream has been received instead of immediately after the transaction is + initiated by the client. + + The value of Request is a Gateway API Duration string as defined by GEP-2257. When this + field is unspecified, request timeout behavior is implementation-specific. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + x-kubernetes-validations: + - message: backendRequest timeout cannot be longer than request + timeout + rule: '!(has(self.request) && has(self.backendRequest) && + duration(self.request) != duration(''0s'') && duration(self.backendRequest) + > duration(self.request))' + type: object + x-kubernetes-validations: + - message: RequestRedirect filter must not be used together with + backendRefs + rule: '(has(self.backendRefs) && size(self.backendRefs) > 0) ? + (!has(self.filters) || self.filters.all(f, !has(f.requestRedirect))): + true' + - message: When using RequestRedirect filter with path.replacePrefixMatch, + exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.requestRedirect) + && has(f.requestRedirect.path) && f.requestRedirect.path.type + == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) + ? ((size(self.matches) != 1 || !has(self.matches[0].path) || + self.matches[0].path.type != ''PathPrefix'') ? false : true) + : true' + - message: When using URLRewrite filter with path.replacePrefixMatch, + exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.urlRewrite) + && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' + && has(f.urlRewrite.path.replacePrefixMatch))) ? ((size(self.matches) + != 1 || !has(self.matches[0].path) || self.matches[0].path.type + != ''PathPrefix'') ? false : true) : true' + - message: Within backendRefs, when using RequestRedirect filter + with path.replacePrefixMatch, exactly one PathPrefix match must + be specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, + (has(b.filters) && b.filters.exists_one(f, has(f.requestRedirect) + && has(f.requestRedirect.path) && f.requestRedirect.path.type + == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) + )) ? ((size(self.matches) != 1 || !has(self.matches[0].path) + || self.matches[0].path.type != ''PathPrefix'') ? false : true) + : true' + - message: Within backendRefs, When using URLRewrite filter with + path.replacePrefixMatch, exactly one PathPrefix match must be + specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, + (has(b.filters) && b.filters.exists_one(f, has(f.urlRewrite) + && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' + && has(f.urlRewrite.path.replacePrefixMatch))) )) ? ((size(self.matches) + != 1 || !has(self.matches[0].path) || self.matches[0].path.type + != ''PathPrefix'') ? false : true) : true' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: While 16 rules and 64 matches per rule are allowed, the + total number of matches across all rules in a route must be less + than 128 + rule: '(self.size() > 0 ? self[0].matches.size() : 0) + (self.size() + > 1 ? self[1].matches.size() : 0) + (self.size() > 2 ? self[2].matches.size() + : 0) + (self.size() > 3 ? self[3].matches.size() : 0) + (self.size() + > 4 ? self[4].matches.size() : 0) + (self.size() > 5 ? self[5].matches.size() + : 0) + (self.size() > 6 ? self[6].matches.size() : 0) + (self.size() + > 7 ? self[7].matches.size() : 0) + (self.size() > 8 ? self[8].matches.size() + : 0) + (self.size() > 9 ? self[9].matches.size() : 0) + (self.size() + > 10 ? self[10].matches.size() : 0) + (self.size() > 11 ? self[11].matches.size() + : 0) + (self.size() > 12 ? self[12].matches.size() : 0) + (self.size() + > 13 ? self[13].matches.size() : 0) + (self.size() > 14 ? self[14].matches.size() + : 0) + (self.size() > 15 ? self[15].matches.size() : 0) <= 128' + - message: Rule name must be unique within the route + rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) + && l1.name == l2.name)) + type: object + status: + description: Status defines the current state of HTTPRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: |- + HTTPRoute provides a way to route HTTP requests. This includes the capability + to match requests by hostname, path, header, or query param. Filters can be + used to specify additional processing steps. Backends specify where matching + requests should be routed. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of HTTPRoute. + properties: + hostnames: + description: |- + Hostnames defines a set of hostnames that should match against the HTTP Host + header to select a HTTPRoute used to process the request. Implementations + MUST ignore any port value specified in the HTTP Host header while + performing a match and (absent of any applicable header modification + configuration) MUST forward this header unmodified to the backend. + + Valid values for Hostnames are determined by RFC 1123 definition of a + hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + If a hostname is specified by both the Listener and HTTPRoute, there + must be at least one intersecting hostname for the HTTPRoute to be + attached to the Listener. For example: + + * A Listener with `test.example.com` as the hostname matches HTTPRoutes + that have either not specified any hostnames, or have specified at + least one of `test.example.com` or `*.example.com`. + * A Listener with `*.example.com` as the hostname matches HTTPRoutes + that have either not specified any hostnames or have specified at least + one hostname that matches the Listener hostname. For example, + `*.example.com`, `test.example.com`, and `foo.test.example.com` would + all match. On the other hand, `example.com` and `test.example.net` would + not match. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + + If both the Listener and HTTPRoute have specified hostnames, any + HTTPRoute hostnames that do not match the Listener hostname MUST be + ignored. For example, if a Listener specified `*.example.com`, and the + HTTPRoute specified `test.example.com` and `test.example.net`, + `test.example.net` must not be considered for a match. + + If both the Listener and HTTPRoute have specified hostnames, and none + match with the criteria above, then the HTTPRoute is not accepted. The + implementation must raise an 'Accepted' Condition with a status of + `False` in the corresponding RouteParentStatus. + + In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. + overlapping wildcard matching and exact matching hostnames), precedence must + be given to rules from the HTTPRoute with the largest number of: + + * Characters in a matching non-wildcard hostname. + * Characters in a matching hostname. + + If ties exist across multiple Routes, the matching precedence rules for + HTTPRouteMatches takes over. + + Support: Core + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) + rules: + default: + - matches: + - path: + type: PathPrefix + value: / + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: |- + HTTPRouteRule defines semantics for matching an HTTP request based on + conditions (matches), processing it (filters), and forwarding the request to + an API object (backendRefs). + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. + + Failure behavior here depends on how many BackendRefs are specified and + how many are invalid. + + If *all* entries in BackendRefs are invalid, and there are also no filters + specified in this route rule, *all* traffic which matches this rule MUST + receive a 500 status code. + + See the HTTPBackendRef definition for the rules about what makes a single + HTTPBackendRef invalid. + + When a HTTPBackendRef is invalid, 500 status codes MUST be returned for + requests that would have otherwise been routed to an invalid backend. If + multiple backends are specified, and some are invalid, the proportion of + requests that would otherwise have been routed to an invalid backend + MUST receive a 500 status code. + + For example, if two backends are specified with equal weights, and one is + invalid, 50 percent of traffic must receive a 500. Implementations may + choose how that 50 percent is determined. + + When a HTTPBackendRef refers to a Service that has no ready endpoints, + implementations SHOULD return a 503 for requests to that backend instead. + If an implementation chooses to do this, all of the above rules for 500 responses + MUST also apply for responses that return a 503. + + Support: Core for Kubernetes Service + + Support: Extended for Kubernetes ServiceImport + + Support: Implementation-specific for any other resource + + Support for weight: Core + items: + description: |- + HTTPBackendRef defines how a HTTPRoute forwards a HTTP request. + + Note that when a namespace different than the local namespace is specified, a + ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + + When the BackendRef points to a Kubernetes Service, implementations SHOULD + honor the appProtocol field if it is set for the target Service Port. + + Implementations supporting appProtocol SHOULD recognize the Kubernetes + Standard Application Protocols defined in KEP-3726. + + If a Service appProtocol isn't specified, an implementation MAY infer the + backend protocol through its own means. Implementations MAY infer the + protocol from the Route type referring to the backend Service. + + If a Route is not able to send traffic to the backend using the specified + protocol then the backend is considered invalid. Implementations MUST set the + "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. + properties: + filters: + description: |- + Filters defined at this level should be executed if and only if the + request is being forwarded to the backend defined here. + + Support: Implementation-specific (For broader support of filters, use the + Filters field in HTTPRouteRule.) + items: + description: |- + HTTPRouteFilter defines processing steps that must be completed during the + request or response lifecycle. HTTPRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + cors: + description: |- + CORS defines a schema for a filter that responds to the + cross-origin request based on HTTP response header. + + Support: Extended + properties: + allowCredentials: + description: |- + AllowCredentials indicates whether the actual cross-origin request allows + to include credentials. + + The only valid value for the `Access-Control-Allow-Credentials` response + header is true (case-sensitive). + + If the credentials are not allowed in cross-origin requests, the gateway + will omit the header `Access-Control-Allow-Credentials` entirely rather + than setting its value to false. + + Support: Extended + enum: + - true + type: boolean + allowHeaders: + description: |- + AllowHeaders indicates which HTTP request headers are supported for + accessing the requested resource. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Allow-Headers` + response header are separated by a comma (","). + + When the `AllowHeaders` field is configured with one or more headers, the + gateway must return the `Access-Control-Allow-Headers` response header + which value is present in the `AllowHeaders` field. + + If any header name in the `Access-Control-Request-Headers` request header + is not included in the list of header names specified by the response + header `Access-Control-Allow-Headers`, it will present an error on the + client side. + + If any header name in the `Access-Control-Allow-Headers` response header + does not recognize by the client, it will also occur an error on the + client side. + + A wildcard indicates that the requests with all HTTP headers are allowed. + The `Access-Control-Allow-Headers` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowHeaders` field + specified with the `*` wildcard, the gateway must specify one or more + HTTP headers in the value of the `Access-Control-Allow-Headers` response + header. The value of the header `Access-Control-Allow-Headers` is same as + the `Access-Control-Request-Headers` header provided by the client. If + the header `Access-Control-Request-Headers` is not included in the + request, the gateway will omit the `Access-Control-Allow-Headers` + response header, instead of specifying the `*` wildcard. A Gateway + implementation may choose to add implementation-specific default headers. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + allowMethods: + description: |- + AllowMethods indicates which HTTP methods are supported for accessing the + requested resource. + + Valid values are any method defined by RFC9110, along with the special + value `*`, which represents all HTTP methods are allowed. + + Method names are case sensitive, so these values are also case-sensitive. + (See https://www.rfc-editor.org/rfc/rfc2616#section-5.1.1) + + Multiple method names in the value of the `Access-Control-Allow-Methods` + response header are separated by a comma (","). + + A CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`. + (See https://fetch.spec.whatwg.org/#cors-safelisted-method) The + CORS-safelisted methods are always allowed, regardless of whether they + are specified in the `AllowMethods` field. + + When the `AllowMethods` field is configured with one or more methods, the + gateway must return the `Access-Control-Allow-Methods` response header + which value is present in the `AllowMethods` field. + + If the HTTP method of the `Access-Control-Request-Method` request header + is not included in the list of methods specified by the response header + `Access-Control-Allow-Methods`, it will present an error on the client + side. + + The `Access-Control-Allow-Methods` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowMethods` field + specified with the `*` wildcard, the gateway must specify one HTTP method + in the value of the Access-Control-Allow-Methods response header. The + value of the header `Access-Control-Allow-Methods` is same as the + `Access-Control-Request-Method` header provided by the client. If the + header `Access-Control-Request-Method` is not included in the request, + the gateway will omit the `Access-Control-Allow-Methods` response header, + instead of specifying the `*` wildcard. A Gateway implementation may + choose to add implementation-specific default methods. + + Support: Extended + items: + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + - '*' + type: string + maxItems: 9 + type: array + x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowMethods cannot contain '*' alongside + other methods + rule: '!(''*'' in self && self.size() > 1)' + allowOrigins: + description: |- + AllowOrigins indicates whether the response can be shared with requested + resource from the given `Origin`. + + The `Origin` consists of a scheme and a host, with an optional port, and + takes the form `://(:)`. + + Valid values for scheme are: `http` and `https`. + + Valid values for port are any integer between 1 and 65535 (the list of + available TCP/UDP ports). Note that, if not included, port `80` is + assumed for `http` scheme origins, and port `443` is assumed for `https` + origins. This may affect origin matching. + + The host part of the origin may contain the wildcard character `*`. These + wildcard characters behave as follows: + + * `*` is a greedy match to the _left_, including any number of + DNS labels to the left of its position. This also means that + `*` will include any number of period `.` characters to the + left of its position. + * A wildcard by itself matches all hosts. + + An origin value that includes _only_ the `*` character indicates requests + from all `Origin`s are allowed. + + When the `AllowOrigins` field is configured with multiple origins, it + means the server supports clients from multiple origins. If the request + `Origin` matches the configured allowed origins, the gateway must return + the given `Origin` and sets value of the header + `Access-Control-Allow-Origin` same as the `Origin` header provided by the + client. + + The status code of a successful response to a "preflight" request is + always an OK status (i.e., 204 or 200). + + If the request `Origin` does not match the configured allowed origins, + the gateway returns 204/200 response but doesn't set the relevant + cross-origin response headers. Alternatively, the gateway responds with + 403 status to the "preflight" request is denied, coupled with omitting + the CORS headers. The cross-origin request fails on the client side. + Therefore, the client doesn't attempt the actual cross-origin request. + + The `Access-Control-Allow-Origin` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowOrigins` field + specified with the `*` wildcard, the gateway must return a single origin + in the value of the `Access-Control-Allow-Origin` response header, + instead of specifying the `*` wildcard. The value of the header + `Access-Control-Allow-Origin` is same as the `Origin` header provided by + the client. + + Support: Extended + items: + description: |- + The AbsoluteURI MUST NOT be a relative URI, and it MUST follow the URI syntax and + encoding rules specified in RFC3986. The AbsoluteURI MUST include both a + scheme (e.g., "http" or "spiffe") and a scheme-specific-part. URIs that + include an authority MUST include a fully qualified domain name or + IP address as the host. + maxLength: 253 + minLength: 1 + pattern: ^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))? + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + exposeHeaders: + description: |- + ExposeHeaders indicates which HTTP response headers can be exposed + to client-side scripts in response to a cross-origin request. + + A CORS-safelisted response header is an HTTP header in a CORS response + that it is considered safe to expose to the client scripts. + The CORS-safelisted response headers include the following headers: + `Cache-Control` + `Content-Language` + `Content-Length` + `Content-Type` + `Expires` + `Last-Modified` + `Pragma` + (See https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name) + The CORS-safelisted response headers are exposed to client by default. + + When an HTTP header name is specified using the `ExposeHeaders` field, + this additional header will be exposed as part of the response to the + client. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Expose-Headers` + response header are separated by a comma (","). + + A wildcard indicates that the responses with all HTTP headers are exposed + to clients. The `Access-Control-Expose-Headers` response header can only + use `*` wildcard as value when the `AllowCredentials` field is + unspecified. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + maxAge: + default: 5 + description: |- + MaxAge indicates the duration (in seconds) for the client to cache the + results of a "preflight" request. + + The information provided by the `Access-Control-Allow-Methods` and + `Access-Control-Allow-Headers` response headers can be cached by the + client until the time specified by `Access-Control-Max-Age` elapses. + + The default value of `Access-Control-Max-Age` response header is 5 + (seconds). + format: int32 + minimum: 1 + type: integer + type: object + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + This filter can be used multiple times within the same rule. + + Support: Implementation-specific + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal + to denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be + specified in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + requestRedirect: + description: |- + RequestRedirect defines a schema for a filter that responds to the + request with an HTTP redirection. + + Support: Core + properties: + hostname: + description: |- + Hostname is the hostname to be used in the value of the `Location` + header in the response. + When empty, the hostname in the `Host` header of the request is used. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines parameters used to modify the path of the incoming request. + The modified path is then used to construct the `Location` header. When + empty, the request path is used as-is. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified + when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? + has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when + replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type + == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified + when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' + ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' + when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + port: + description: |- + Port is the port to be used in the value of the `Location` + header in the response. + + If no port is specified, the redirect port MUST be derived using the + following rules: + + * If redirect scheme is not-empty, the redirect port MUST be the well-known + port associated with the redirect scheme. Specifically "http" to port 80 + and "https" to port 443. If the redirect scheme does not have a + well-known port, the listener port of the Gateway SHOULD be used. + * If redirect scheme is empty, the redirect port MUST be the Gateway + Listener port. + + Implementations SHOULD NOT add the port number in the 'Location' + header in the following cases: + + * A Location header that will use HTTP (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 80. + * A Location header that will use HTTPS (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 443. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: |- + Scheme is the scheme to be used in the value of the `Location` header in + the response. When empty, the scheme of the request is used. + + Scheme redirects can affect the port of the redirect, for more information, + refer to the documentation for the port field of this filter. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Extended + enum: + - http + - https + type: string + statusCode: + default: 302 + description: |- + StatusCode is the HTTP status code to be used in response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Core + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations must support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by + specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` should be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + - CORS + type: string + urlRewrite: + description: |- + URLRewrite defines a schema for a filter that modifies a request during forwarding. + + Support: Extended + properties: + hostname: + description: |- + Hostname is the value to be used to replace the Host header value during + forwarding. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines a path rewrite. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified + when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? + has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when + replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type + == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified + when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' + ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' + when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil + if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type + != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type + == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil + if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type + != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for + RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == + ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the + filter.type is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != + ''RequestRedirect'')' + - message: filter.requestRedirect must be specified + for RequestRedirect filter.type + rule: '!(!has(self.requestRedirect) && self.type == + ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type + is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite + filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for + ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + - message: filter.cors must be nil if the filter.type + is not CORS + rule: '!(has(self.cors) && self.type != ''CORS'')' + - message: filter.cors must be specified for CORS filter.type + rule: '!(!has(self.cors) && self.type == ''CORS'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') + && self.exists(f, f.type == ''URLRewrite''))' + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') + && self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() + <= 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() + <= 1 + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + filters: + description: |- + Filters define the filters that are applied to requests that match + this rule. + + Wherever possible, implementations SHOULD implement filters in the order + they are specified. + + Implementations MAY choose to implement this ordering strictly, rejecting + any combination or order of filters that cannot be supported. If implementations + choose a strict interpretation of filter ordering, they MUST clearly document + that behavior. + + To reject an invalid combination or order of filters, implementations SHOULD + consider the Route Rules with this configuration invalid. If all Route Rules + in a Route are invalid, the entire Route would be considered invalid. If only + a portion of Route Rules are invalid, implementations MUST set the + "PartiallyInvalid" condition for the Route. + + Conformance-levels at this level are defined based on the type of filter: + + - ALL core filters MUST be supported by all implementations. + - Implementers are encouraged to support extended filters. + - Implementation-specific custom filters have no API guarantees across + implementations. + + Specifying the same filter multiple times is not supported unless explicitly + indicated in the filter. + + All filters are expected to be compatible with each other except for the + URLRewrite and RequestRedirect filters, which may not be combined. If an + implementation cannot support other combinations of filters, they must clearly + document that limitation. In cases where incompatible or unsupported + filters are specified and cause the `Accepted` condition to be set to status + `False`, implementations may use the `IncompatibleFilters` reason to specify + this configuration error. + + Support: Core + items: + description: |- + HTTPRouteFilter defines processing steps that must be completed during the + request or response lifecycle. HTTPRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + cors: + description: |- + CORS defines a schema for a filter that responds to the + cross-origin request based on HTTP response header. + + Support: Extended + properties: + allowCredentials: + description: |- + AllowCredentials indicates whether the actual cross-origin request allows + to include credentials. + + The only valid value for the `Access-Control-Allow-Credentials` response + header is true (case-sensitive). + + If the credentials are not allowed in cross-origin requests, the gateway + will omit the header `Access-Control-Allow-Credentials` entirely rather + than setting its value to false. + + Support: Extended + enum: + - true + type: boolean + allowHeaders: + description: |- + AllowHeaders indicates which HTTP request headers are supported for + accessing the requested resource. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Allow-Headers` + response header are separated by a comma (","). + + When the `AllowHeaders` field is configured with one or more headers, the + gateway must return the `Access-Control-Allow-Headers` response header + which value is present in the `AllowHeaders` field. + + If any header name in the `Access-Control-Request-Headers` request header + is not included in the list of header names specified by the response + header `Access-Control-Allow-Headers`, it will present an error on the + client side. + + If any header name in the `Access-Control-Allow-Headers` response header + does not recognize by the client, it will also occur an error on the + client side. + + A wildcard indicates that the requests with all HTTP headers are allowed. + The `Access-Control-Allow-Headers` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowHeaders` field + specified with the `*` wildcard, the gateway must specify one or more + HTTP headers in the value of the `Access-Control-Allow-Headers` response + header. The value of the header `Access-Control-Allow-Headers` is same as + the `Access-Control-Request-Headers` header provided by the client. If + the header `Access-Control-Request-Headers` is not included in the + request, the gateway will omit the `Access-Control-Allow-Headers` + response header, instead of specifying the `*` wildcard. A Gateway + implementation may choose to add implementation-specific default headers. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + allowMethods: + description: |- + AllowMethods indicates which HTTP methods are supported for accessing the + requested resource. + + Valid values are any method defined by RFC9110, along with the special + value `*`, which represents all HTTP methods are allowed. + + Method names are case sensitive, so these values are also case-sensitive. + (See https://www.rfc-editor.org/rfc/rfc2616#section-5.1.1) + + Multiple method names in the value of the `Access-Control-Allow-Methods` + response header are separated by a comma (","). + + A CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`. + (See https://fetch.spec.whatwg.org/#cors-safelisted-method) The + CORS-safelisted methods are always allowed, regardless of whether they + are specified in the `AllowMethods` field. + + When the `AllowMethods` field is configured with one or more methods, the + gateway must return the `Access-Control-Allow-Methods` response header + which value is present in the `AllowMethods` field. + + If the HTTP method of the `Access-Control-Request-Method` request header + is not included in the list of methods specified by the response header + `Access-Control-Allow-Methods`, it will present an error on the client + side. + + The `Access-Control-Allow-Methods` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowMethods` field + specified with the `*` wildcard, the gateway must specify one HTTP method + in the value of the Access-Control-Allow-Methods response header. The + value of the header `Access-Control-Allow-Methods` is same as the + `Access-Control-Request-Method` header provided by the client. If the + header `Access-Control-Request-Method` is not included in the request, + the gateway will omit the `Access-Control-Allow-Methods` response header, + instead of specifying the `*` wildcard. A Gateway implementation may + choose to add implementation-specific default methods. + + Support: Extended + items: + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + - '*' + type: string + maxItems: 9 + type: array + x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowMethods cannot contain '*' alongside + other methods + rule: '!(''*'' in self && self.size() > 1)' + allowOrigins: + description: |- + AllowOrigins indicates whether the response can be shared with requested + resource from the given `Origin`. + + The `Origin` consists of a scheme and a host, with an optional port, and + takes the form `://(:)`. + + Valid values for scheme are: `http` and `https`. + + Valid values for port are any integer between 1 and 65535 (the list of + available TCP/UDP ports). Note that, if not included, port `80` is + assumed for `http` scheme origins, and port `443` is assumed for `https` + origins. This may affect origin matching. + + The host part of the origin may contain the wildcard character `*`. These + wildcard characters behave as follows: + + * `*` is a greedy match to the _left_, including any number of + DNS labels to the left of its position. This also means that + `*` will include any number of period `.` characters to the + left of its position. + * A wildcard by itself matches all hosts. + + An origin value that includes _only_ the `*` character indicates requests + from all `Origin`s are allowed. + + When the `AllowOrigins` field is configured with multiple origins, it + means the server supports clients from multiple origins. If the request + `Origin` matches the configured allowed origins, the gateway must return + the given `Origin` and sets value of the header + `Access-Control-Allow-Origin` same as the `Origin` header provided by the + client. + + The status code of a successful response to a "preflight" request is + always an OK status (i.e., 204 or 200). + + If the request `Origin` does not match the configured allowed origins, + the gateway returns 204/200 response but doesn't set the relevant + cross-origin response headers. Alternatively, the gateway responds with + 403 status to the "preflight" request is denied, coupled with omitting + the CORS headers. The cross-origin request fails on the client side. + Therefore, the client doesn't attempt the actual cross-origin request. + + The `Access-Control-Allow-Origin` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowOrigins` field + specified with the `*` wildcard, the gateway must return a single origin + in the value of the `Access-Control-Allow-Origin` response header, + instead of specifying the `*` wildcard. The value of the header + `Access-Control-Allow-Origin` is same as the `Origin` header provided by + the client. + + Support: Extended + items: + description: |- + The AbsoluteURI MUST NOT be a relative URI, and it MUST follow the URI syntax and + encoding rules specified in RFC3986. The AbsoluteURI MUST include both a + scheme (e.g., "http" or "spiffe") and a scheme-specific-part. URIs that + include an authority MUST include a fully qualified domain name or + IP address as the host. + maxLength: 253 + minLength: 1 + pattern: ^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))? + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + exposeHeaders: + description: |- + ExposeHeaders indicates which HTTP response headers can be exposed + to client-side scripts in response to a cross-origin request. + + A CORS-safelisted response header is an HTTP header in a CORS response + that it is considered safe to expose to the client scripts. + The CORS-safelisted response headers include the following headers: + `Cache-Control` + `Content-Language` + `Content-Length` + `Content-Type` + `Expires` + `Last-Modified` + `Pragma` + (See https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name) + The CORS-safelisted response headers are exposed to client by default. + + When an HTTP header name is specified using the `ExposeHeaders` field, + this additional header will be exposed as part of the response to the + client. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Expose-Headers` + response header are separated by a comma (","). + + A wildcard indicates that the responses with all HTTP headers are exposed + to clients. The `Access-Control-Expose-Headers` response header can only + use `*` wildcard as value when the `AllowCredentials` field is + unspecified. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + maxAge: + default: 5 + description: |- + MaxAge indicates the duration (in seconds) for the client to cache the + results of a "preflight" request. + + The information provided by the `Access-Control-Allow-Methods` and + `Access-Control-Allow-Headers` response headers can be cached by the + client until the time specified by `Access-Control-Max-Age` elapses. + + The default value of `Access-Control-Max-Age` response header is 5 + (seconds). + format: int32 + minimum: 1 + type: integer + type: object + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + This filter can be used multiple times within the same rule. + + Support: Implementation-specific + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal to + denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be specified + in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + requestRedirect: + description: |- + RequestRedirect defines a schema for a filter that responds to the + request with an HTTP redirection. + + Support: Core + properties: + hostname: + description: |- + Hostname is the hostname to be used in the value of the `Location` + header in the response. + When empty, the hostname in the `Host` header of the request is used. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines parameters used to modify the path of the incoming request. + The modified path is then used to construct the `Location` header. When + empty, the request path is used as-is. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when + type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) + : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath + is set + rule: 'has(self.replaceFullPath) ? self.type == + ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when + type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) + : true' + - message: type must be 'ReplacePrefixMatch' when + replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + port: + description: |- + Port is the port to be used in the value of the `Location` + header in the response. + + If no port is specified, the redirect port MUST be derived using the + following rules: + + * If redirect scheme is not-empty, the redirect port MUST be the well-known + port associated with the redirect scheme. Specifically "http" to port 80 + and "https" to port 443. If the redirect scheme does not have a + well-known port, the listener port of the Gateway SHOULD be used. + * If redirect scheme is empty, the redirect port MUST be the Gateway + Listener port. + + Implementations SHOULD NOT add the port number in the 'Location' + header in the following cases: + + * A Location header that will use HTTP (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 80. + * A Location header that will use HTTPS (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 443. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: |- + Scheme is the scheme to be used in the value of the `Location` header in + the response. When empty, the scheme of the request is used. + + Scheme redirects can affect the port of the redirect, for more information, + refer to the documentation for the port field of this filter. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Extended + enum: + - http + - https + type: string + statusCode: + default: 302 + description: |- + StatusCode is the HTTP status code to be used in response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Core + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations must support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by + specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` should be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + - CORS + type: string + urlRewrite: + description: |- + URLRewrite defines a schema for a filter that modifies a request during forwarding. + + Support: Extended + properties: + hostname: + description: |- + Hostname is the value to be used to replace the Host header value during + forwarding. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines a path rewrite. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when + type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) + : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath + is set + rule: 'has(self.replaceFullPath) ? self.type == + ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when + type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) + : true' + - message: type must be 'ReplacePrefixMatch' when + replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the + filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != + ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == + ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the + filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != + ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror + filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the filter.type + is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')' + - message: filter.requestRedirect must be specified for RequestRedirect + filter.type + rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type + is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite + filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef + filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + - message: filter.cors must be nil if the filter.type is not + CORS + rule: '!(has(self.cors) && self.type != ''CORS'')' + - message: filter.cors must be specified for CORS filter.type + rule: '!(!has(self.cors) && self.type == ''CORS'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') && + self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() <= + 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() <= 1 + matches: + default: + - path: + type: PathPrefix + value: / + description: |- + Matches define conditions used for matching the rule against incoming + HTTP requests. Each match is independent, i.e. this rule will be matched + if **any** one of the matches is satisfied. + + For example, take the following matches configuration: + + ``` + matches: + - path: + value: "/foo" + headers: + - name: "version" + value: "v2" + - path: + value: "/v2/foo" + ``` + + For a request to match against this rule, a request must satisfy + EITHER of the two conditions: + + - path prefixed with `/foo` AND contains the header `version: v2` + - path prefix of `/v2/foo` + + See the documentation for HTTPRouteMatch on how to specify multiple + match conditions that should be ANDed together. + + If no matches are specified, the default is a prefix + path match on "/", which has the effect of matching every + HTTP request. + + Proxy or Load Balancer routing configuration generated from HTTPRoutes + MUST prioritize matches based on the following criteria, continuing on + ties. Across all rules specified on applicable Routes, precedence must be + given to the match having: + + * "Exact" path match. + * "Prefix" path match with largest number of characters. + * Method match. + * Largest number of header matches. + * Largest number of query param matches. + + Note: The precedence of RegularExpression path matches are implementation-specific. + + If ties still exist across multiple Routes, matching precedence MUST be + determined in order of the following criteria, continuing on ties: + + * The oldest Route based on creation timestamp. + * The Route appearing first in alphabetical order by + "{namespace}/{name}". + + If ties still exist within an HTTPRoute, matching precedence MUST be granted + to the FIRST matching rule (in list order) with a match meeting the above + criteria. + + When no rules matching a request have been successfully attached to the + parent a request is coming from, a HTTP 404 status code MUST be returned. + items: + description: "HTTPRouteMatch defines the predicate used to + match requests to a given\naction. Multiple match types + are ANDed together, i.e. the match will\nevaluate to true + only if all conditions are satisfied.\n\nFor example, the + match below will match a HTTP request only if its path\nstarts + with `/foo` AND it contains the `version: v1` header:\n\n```\nmatch:\n\n\tpath:\n\t + \ value: \"/foo\"\n\theaders:\n\t- name: \"version\"\n\t + \ value \"v1\"\n\n```" + properties: + headers: + description: |- + Headers specifies HTTP request header matchers. Multiple match values are + ANDed together, meaning, a request must match all the specified headers + to select the route. + items: + description: |- + HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request + headers. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + + When a header is repeated in an HTTP request, it is + implementation-specific behavior as to how this is represented. + Generally, proxies should follow the guidance from the RFC: + https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding + processing a repeated header, with special handling for "Set-Cookie". + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the header. + + Support: Core (Exact) + + Support: Implementation-specific (RegularExpression) + + Since RegularExpression HeaderMatchType has implementation-specific + conformance, implementations can support POSIX, PCRE or any other dialects + of regular expressions. Please read the implementation's documentation to + determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP Header to + be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: |- + Method specifies HTTP method matcher. + When specified, this route will be matched only if the request has the + specified method. + + Support: Extended + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: |- + Path specifies a HTTP request path matcher. If this field is not + specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: |- + Type specifies how to match against the path Value. + + Support: Core (Exact, PathPrefix) + + Support: Implementation-specific (RegularExpression) + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path to match against. + maxLength: 1024 + type: string + type: object + x-kubernetes-validations: + - message: value must be an absolute path and start with + '/' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.startsWith(''/'') + : true' + - message: must not contain '//' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''//'') + : true' + - message: must not contain '/./' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/./'') + : true' + - message: must not contain '/../' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/../'') + : true' + - message: must not contain '%2f' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2f'') + : true' + - message: must not contain '%2F' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2F'') + : true' + - message: must not contain '#' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''#'') + : true' + - message: must not end with '/..' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/..'') + : true' + - message: must not end with '/.' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/.'') + : true' + - message: type must be one of ['Exact', 'PathPrefix', + 'RegularExpression'] + rule: self.type in ['Exact','PathPrefix'] || self.type + == 'RegularExpression' + - message: must only contain valid characters (matching + ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$) + for types ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.matches(r"""^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$""") + : true' + queryParams: + description: |- + QueryParams specifies HTTP query parameter matchers. Multiple match + values are ANDed together, meaning, a request must match all the + specified query parameters to select the route. + + Support: Extended + items: + description: |- + HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP + query parameters. + properties: + name: + description: |- + Name is the name of the HTTP query param to be matched. This must be an + exact string match. (See + https://tools.ietf.org/html/rfc7230#section-2.7.3). + + If multiple entries specify equivalent query param names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent query param name MUST be ignored. + + If a query param is repeated in an HTTP request, the behavior is + purposely left undefined, since different data planes have different + capabilities. However, it is *recommended* that implementations should + match against the first value of the param if the data plane supports it, + as this behavior is expected in other load balancing contexts outside of + the Gateway API. + + Users SHOULD NOT route traffic based on repeated query params to guard + themselves against potential differences in the implementations. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the query parameter. + + Support: Extended (Exact) + + Support: Implementation-specific (RegularExpression) + + Since RegularExpression QueryParamMatchType has Implementation-specific + conformance, implementations can support POSIX, PCRE or any other + dialects of regular expressions. Please read the implementation's + documentation to determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP query param + to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 64 + type: array + name: + description: |- + Name is the name of the route rule. This name MUST be unique within a Route if it is set. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + retry: + description: |- + Retry defines the configuration for when to retry an HTTP request. + + Support: Extended + properties: + attempts: + description: |- + Attempts specifies the maximum number of times an individual request + from the gateway to a backend should be retried. + + If the maximum number of retries has been attempted without a successful + response from the backend, the Gateway MUST return an error. + + When this field is unspecified, the number of times to attempt to retry + a backend request is implementation-specific. + + Support: Extended + type: integer + backoff: + description: |- + Backoff specifies the minimum duration a Gateway should wait between + retry attempts and is represented in Gateway API Duration formatting. + + For example, setting the `rules[].retry.backoff` field to the value + `100ms` will cause a backend request to first be retried approximately + 100 milliseconds after timing out or receiving a response code configured + to be retryable. + + An implementation MAY use an exponential or alternative backoff strategy + for subsequent retry attempts, MAY cap the maximum backoff duration to + some amount greater than the specified minimum, and MAY add arbitrary + jitter to stagger requests, as long as unsuccessful backend requests are + not retried before the configured minimum duration. + + If a Request timeout (`rules[].timeouts.request`) is configured on the + route, the entire duration of the initial request and any retry attempts + MUST not exceed the Request timeout duration. If any retry attempts are + still in progress when the Request timeout duration has been reached, + these SHOULD be canceled if possible and the Gateway MUST immediately + return a timeout error. + + If a BackendRequest timeout (`rules[].timeouts.backendRequest`) is + configured on the route, any retry attempts which reach the configured + BackendRequest timeout duration without a response SHOULD be canceled if + possible and the Gateway should wait for at least the specified backoff + duration before attempting to retry the backend request again. + + If a BackendRequest timeout is _not_ configured on the route, retry + attempts MAY time out after an implementation default duration, or MAY + remain pending until a configured Request timeout or implementation + default duration for total request time is reached. + + When this field is unspecified, the time to wait between retry attempts + is implementation-specific. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + codes: + description: |- + Codes defines the HTTP response status codes for which a backend request + should be retried. + + Support: Extended + items: + description: |- + HTTPRouteRetryStatusCode defines an HTTP response status code for + which a backend request should be retried. + + Implementations MUST support the following status codes as retryable: + + * 500 + * 502 + * 503 + * 504 + + Implementations MAY support specifying additional discrete values in the + 500-599 range. + + Implementations MAY support specifying discrete values in the 400-499 range, + which are often inadvisable to retry. + maximum: 599 + minimum: 400 + type: integer + type: array + type: object + sessionPersistence: + description: |- + SessionPersistence defines and configures session persistence + for the route rule. + + Support: Extended + properties: + absoluteTimeout: + description: |- + AbsoluteTimeout defines the absolute timeout of the persistent + session. Once the AbsoluteTimeout duration has elapsed, the + session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + cookieConfig: + description: |- + CookieConfig provides configuration settings that are specific + to cookie-based session persistence. + + Support: Core + properties: + lifetimeType: + default: Session + description: |- + LifetimeType specifies whether the cookie has a permanent or + session-based lifetime. A permanent cookie persists until its + specified expiry time, defined by the Expires or Max-Age cookie + attributes, while a session cookie is deleted when the current + session ends. + + When set to "Permanent", AbsoluteTimeout indicates the + cookie's lifetime via the Expires or Max-Age cookie attributes + and is required. + + When set to "Session", AbsoluteTimeout indicates the + absolute lifetime of the cookie tracked by the gateway and + is optional. + + Defaults to "Session". + + Support: Core for "Session" type + + Support: Extended for "Permanent" type + enum: + - Permanent + - Session + type: string + type: object + idleTimeout: + description: |- + IdleTimeout defines the idle timeout of the persistent session. + Once the session has been idle for more than the specified + IdleTimeout duration, the session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + sessionName: + description: |- + SessionName defines the name of the persistent session token + which may be reflected in the cookie or the header. Users + should avoid reusing session names to prevent unintended + consequences, such as rejection or unpredictable behavior. + + Support: Implementation-specific + maxLength: 128 + type: string + type: + default: Cookie + description: |- + Type defines the type of session persistence such as through + the use a header or cookie. Defaults to cookie based session + persistence. + + Support: Core for "Cookie" type + + Support: Extended for "Header" type + enum: + - Cookie + - Header + type: string + type: object + x-kubernetes-validations: + - message: AbsoluteTimeout must be specified when cookie lifetimeType + is Permanent + rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType) + || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)' + timeouts: + description: |- + Timeouts defines the timeouts that can be configured for an HTTP request. + + Support: Extended + properties: + backendRequest: + description: |- + BackendRequest specifies a timeout for an individual request from the gateway + to a backend. This covers the time from when the request first starts being + sent from the gateway to when the full response has been received from the backend. + + Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout + completely. Implementations that cannot completely disable the timeout MUST + instead interpret the zero duration as the longest possible value to which + the timeout can be set. + + An entire client HTTP transaction with a gateway, covered by the Request timeout, + may result in more than one call from the gateway to the destination backend, + for example, if automatic retries are supported. + + The value of BackendRequest must be a Gateway API Duration string as defined by + GEP-2257. When this field is unspecified, its behavior is implementation-specific; + when specified, the value of BackendRequest must be no more than the value of the + Request timeout (since the Request timeout encompasses the BackendRequest timeout). + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + request: + description: |- + Request specifies the maximum duration for a gateway to respond to an HTTP request. + If the gateway has not been able to respond before this deadline is met, the gateway + MUST return a timeout error. + + For example, setting the `rules.timeouts.request` field to the value `10s` in an + `HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds + to complete. + + Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout + completely. Implementations that cannot completely disable the timeout MUST + instead interpret the zero duration as the longest possible value to which + the timeout can be set. + + This timeout is intended to cover as close to the whole request-response transaction + as possible although an implementation MAY choose to start the timeout after the entire + request stream has been received instead of immediately after the transaction is + initiated by the client. + + The value of Request is a Gateway API Duration string as defined by GEP-2257. When this + field is unspecified, request timeout behavior is implementation-specific. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + x-kubernetes-validations: + - message: backendRequest timeout cannot be longer than request + timeout + rule: '!(has(self.request) && has(self.backendRequest) && + duration(self.request) != duration(''0s'') && duration(self.backendRequest) + > duration(self.request))' + type: object + x-kubernetes-validations: + - message: RequestRedirect filter must not be used together with + backendRefs + rule: '(has(self.backendRefs) && size(self.backendRefs) > 0) ? + (!has(self.filters) || self.filters.all(f, !has(f.requestRedirect))): + true' + - message: When using RequestRedirect filter with path.replacePrefixMatch, + exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.requestRedirect) + && has(f.requestRedirect.path) && f.requestRedirect.path.type + == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) + ? ((size(self.matches) != 1 || !has(self.matches[0].path) || + self.matches[0].path.type != ''PathPrefix'') ? false : true) + : true' + - message: When using URLRewrite filter with path.replacePrefixMatch, + exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.urlRewrite) + && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' + && has(f.urlRewrite.path.replacePrefixMatch))) ? ((size(self.matches) + != 1 || !has(self.matches[0].path) || self.matches[0].path.type + != ''PathPrefix'') ? false : true) : true' + - message: Within backendRefs, when using RequestRedirect filter + with path.replacePrefixMatch, exactly one PathPrefix match must + be specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, + (has(b.filters) && b.filters.exists_one(f, has(f.requestRedirect) + && has(f.requestRedirect.path) && f.requestRedirect.path.type + == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) + )) ? ((size(self.matches) != 1 || !has(self.matches[0].path) + || self.matches[0].path.type != ''PathPrefix'') ? false : true) + : true' + - message: Within backendRefs, When using URLRewrite filter with + path.replacePrefixMatch, exactly one PathPrefix match must be + specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, + (has(b.filters) && b.filters.exists_one(f, has(f.urlRewrite) + && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' + && has(f.urlRewrite.path.replacePrefixMatch))) )) ? ((size(self.matches) + != 1 || !has(self.matches[0].path) || self.matches[0].path.type + != ''PathPrefix'') ? false : true) : true' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: While 16 rules and 64 matches per rule are allowed, the + total number of matches across all rules in a route must be less + than 128 + rule: '(self.size() > 0 ? self[0].matches.size() : 0) + (self.size() + > 1 ? self[1].matches.size() : 0) + (self.size() > 2 ? self[2].matches.size() + : 0) + (self.size() > 3 ? self[3].matches.size() : 0) + (self.size() + > 4 ? self[4].matches.size() : 0) + (self.size() > 5 ? self[5].matches.size() + : 0) + (self.size() > 6 ? self[6].matches.size() : 0) + (self.size() + > 7 ? self[7].matches.size() : 0) + (self.size() > 8 ? self[8].matches.size() + : 0) + (self.size() > 9 ? self[9].matches.size() : 0) + (self.size() + > 10 ? self[10].matches.size() : 0) + (self.size() > 11 ? self[11].matches.size() + : 0) + (self.size() > 12 ? self[12].matches.size() : 0) + (self.size() + > 13 ? self[13].matches.size() : 0) + (self.size() > 14 ? self[14].matches.size() + : 0) + (self.size() > 15 ? self[15].matches.size() : 0) <= 128' + - message: Rule name must be unique within the route + rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) + && l1.name == l2.name)) + type: object + status: + description: Status defines the current state of HTTPRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_referencegrants.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: referencegrants.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: ReferenceGrant + listKind: ReferenceGrantList + plural: referencegrants + shortNames: + - refgrant + singular: referencegrant + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: |- + ReferenceGrant identifies kinds of resources in other namespaces that are + trusted to reference the specified kinds of resources in the same namespace + as the policy. + + Each ReferenceGrant can be used to represent a unique trust relationship. + Additional Reference Grants can be used to add to the set of trusted + sources of inbound references for the namespace they are defined within. + + All cross-namespace references in Gateway API (with the exception of cross-namespace + Gateway-route attachment) require a ReferenceGrant. + + ReferenceGrant is a form of runtime verification allowing users to assert + which cross-namespace object references are permitted. Implementations that + support ReferenceGrant MUST NOT permit cross-namespace references which have + no grant, and MUST respond to the removal of a grant by revoking the access + that the grant allowed. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of ReferenceGrant. + properties: + from: + description: |- + From describes the trusted namespaces and kinds that can reference the + resources described in "To". Each entry in this list MUST be considered + to be an additional place that references can be valid from, or to put + this another way, entries MUST be combined using OR. + + Support: Core + items: + description: ReferenceGrantFrom describes trusted namespaces and + kinds. + properties: + group: + description: |- + Group is the group of the referent. + When empty, the Kubernetes core API group is inferred. + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: |- + Kind is the kind of the referent. Although implementations may support + additional resources, the following types are part of the "Core" + support level for this field. + + When used to permit a SecretObjectReference: + + * Gateway + + When used to permit a BackendObjectReference: + + * GRPCRoute + * HTTPRoute + * TCPRoute + * TLSRoute + * UDPRoute + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + namespace: + description: |- + Namespace is the namespace of the referent. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - namespace + type: object + maxItems: 16 + minItems: 1 + type: array + to: + description: |- + To describes the resources that may be referenced by the resources + described in "From". Each entry in this list MUST be considered to be an + additional place that references can be valid to, or to put this another + way, entries MUST be combined using OR. + + Support: Core + items: + description: |- + ReferenceGrantTo describes what Kinds are allowed as targets of the + references. + properties: + group: + description: |- + Group is the group of the referent. + When empty, the Kubernetes core API group is inferred. + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: |- + Kind is the kind of the referent. Although implementations may support + additional resources, the following types are part of the "Core" + support level for this field: + + * Secret when used to permit a SecretObjectReference + * Service when used to permit a BackendObjectReference + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. When unspecified, this policy + refers to all resources of the specified Group and Kind in the local + namespace. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - from + - to + type: object + type: object + served: true + storage: true + subresources: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_tcproutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: tcproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: TCPRoute + listKind: TCPRouteList + plural: tcproutes + singular: tcproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: |- + TCPRoute provides a way to route TCP requests. When combined with a Gateway + listener, it can be used to forward connections on the port specified by the + listener to a set of backends specified by the TCPRoute. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of TCPRoute. + properties: + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) + rules: + description: Rules are a list of TCP matchers and actions. + items: + description: TCPRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. If unspecified or invalid (refers to a nonexistent resource or a + Service with no endpoints), the underlying implementation MUST actively + reject connection attempts to this backend. Connection rejections must + respect weight; if an invalid backend is requested to have 80% of + connections, then 80% of connections must be rejected instead. + + Support: Core for Kubernetes Service + + Support: Extended for Kubernetes ServiceImport + + Support: Implementation-specific for any other resource + + Support for weight: Extended + items: + description: |- + BackendRef defines how a Route should forward a request to a Kubernetes + resource. + + Note that when a namespace different than the local namespace is specified, a + ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + + When the BackendRef points to a Kubernetes Service, implementations SHOULD + honor the appProtocol field if it is set for the target Service Port. + + Implementations supporting appProtocol SHOULD recognize the Kubernetes + Standard Application Protocols defined in KEP-3726. + + If a Service appProtocol isn't specified, an implementation MAY infer the + backend protocol through its own means. Implementations MAY infer the + protocol from the Route type referring to the backend Service. + + If a Route is not able to send traffic to the backend using the specified + protocol then the backend is considered invalid. Implementations MUST set the + "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. + + + Note that when the BackendTLSPolicy object is enabled by the implementation, + there are some extra rules about validity to consider here. See the fields + where this struct is used for more information about the exact behavior. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + minItems: 1 + type: array + name: + description: |- + Name is the name of the route rule. This name MUST be unique within a Route if it is set. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-validations: + - message: Rule name must be unique within the route + rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) + && l1.name == l2.name)) + required: + - rules + type: object + status: + description: Status defines the current state of TCPRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_tlsroutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: tlsroutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: TLSRoute + listKind: TLSRouteList + plural: tlsroutes + singular: tlsroute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: |- + The TLSRoute resource is similar to TCPRoute, but can be configured + to match against TLS-specific metadata. This allows more flexibility + in matching streams for a given TLS listener. + + If you need to forward traffic to a single target for a TLS listener, you + could choose to use a TCPRoute with a TLS listener. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of TLSRoute. + properties: + hostnames: + description: |- + Hostnames defines a set of SNI names that should match against the + SNI attribute of TLS ClientHello message in TLS handshake. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed in SNI names per RFC 6066. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + If a hostname is specified by both the Listener and TLSRoute, there + must be at least one intersecting hostname for the TLSRoute to be + attached to the Listener. For example: + + * A Listener with `test.example.com` as the hostname matches TLSRoutes + that have either not specified any hostnames, or have specified at + least one of `test.example.com` or `*.example.com`. + * A Listener with `*.example.com` as the hostname matches TLSRoutes + that have either not specified any hostnames or have specified at least + one hostname that matches the Listener hostname. For example, + `test.example.com` and `*.example.com` would both match. On the other + hand, `example.com` and `test.example.net` would not match. + + If both the Listener and TLSRoute have specified hostnames, any + TLSRoute hostnames that do not match the Listener hostname MUST be + ignored. For example, if a Listener specified `*.example.com`, and the + TLSRoute specified `test.example.com` and `test.example.net`, + `test.example.net` must not be considered for a match. + + If both the Listener and TLSRoute have specified hostnames, and none + match with the criteria above, then the TLSRoute is not accepted. The + implementation must raise an 'Accepted' Condition with a status of + `False` in the corresponding RouteParentStatus. + + Support: Core + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) + rules: + description: Rules are a list of TLS matchers and actions. + items: + description: TLSRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. If unspecified or invalid (refers to a nonexistent resource or + a Service with no endpoints), the rule performs no forwarding; if no + filters are specified that would result in a response being sent, the + underlying implementation must actively reject request attempts to this + backend, by rejecting the connection or returning a 500 status code. + Request rejections must respect weight; if an invalid backend is + requested to have 80% of requests, then 80% of requests must be rejected + instead. + + Support: Core for Kubernetes Service + + Support: Extended for Kubernetes ServiceImport + + Support: Implementation-specific for any other resource + + Support for weight: Extended + items: + description: |- + BackendRef defines how a Route should forward a request to a Kubernetes + resource. + + Note that when a namespace different than the local namespace is specified, a + ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + + When the BackendRef points to a Kubernetes Service, implementations SHOULD + honor the appProtocol field if it is set for the target Service Port. + + Implementations supporting appProtocol SHOULD recognize the Kubernetes + Standard Application Protocols defined in KEP-3726. + + If a Service appProtocol isn't specified, an implementation MAY infer the + backend protocol through its own means. Implementations MAY infer the + protocol from the Route type referring to the backend Service. + + If a Route is not able to send traffic to the backend using the specified + protocol then the backend is considered invalid. Implementations MUST set the + "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. + + + Note that when the BackendTLSPolicy object is enabled by the implementation, + there are some extra rules about validity to consider here. See the fields + where this struct is used for more information about the exact behavior. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + minItems: 1 + type: array + name: + description: |- + Name is the name of the route rule. This name MUST be unique within a Route if it is set. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-validations: + - message: Rule name must be unique within the route + rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) + && l1.name == l2.name)) + required: + - rules + type: object + status: + description: Status defines the current state of TLSRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_udproutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: udproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: UDPRoute + listKind: UDPRouteList + plural: udproutes + singular: udproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: |- + UDPRoute provides a way to route UDP traffic. When combined with a Gateway + listener, it can be used to forward traffic on the port specified by the + listener to a set of backends specified by the UDPRoute. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of UDPRoute. + properties: + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) + rules: + description: Rules are a list of UDP matchers and actions. + items: + description: UDPRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. If unspecified or invalid (refers to a nonexistent resource or a + Service with no endpoints), the underlying implementation MUST actively + reject connection attempts to this backend. Packet drops must + respect weight; if an invalid backend is requested to have 80% of + the packets, then 80% of packets must be dropped instead. + + Support: Core for Kubernetes Service + + Support: Extended for Kubernetes ServiceImport + + Support: Implementation-specific for any other resource + + Support for weight: Extended + items: + description: |- + BackendRef defines how a Route should forward a request to a Kubernetes + resource. + + Note that when a namespace different than the local namespace is specified, a + ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + + When the BackendRef points to a Kubernetes Service, implementations SHOULD + honor the appProtocol field if it is set for the target Service Port. + + Implementations supporting appProtocol SHOULD recognize the Kubernetes + Standard Application Protocols defined in KEP-3726. + + If a Service appProtocol isn't specified, an implementation MAY infer the + backend protocol through its own means. Implementations MAY infer the + protocol from the Route type referring to the backend Service. + + If a Route is not able to send traffic to the backend using the specified + protocol then the backend is considered invalid. Implementations MUST set the + "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. + + + Note that when the BackendTLSPolicy object is enabled by the implementation, + there are some extra rules about validity to consider here. See the fields + where this struct is used for more information about the exact behavior. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + minItems: 1 + type: array + name: + description: |- + Name is the name of the route rule. This name MUST be unique within a Route if it is set. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-validations: + - message: Rule name must be unique within the route + rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) + && l1.name == l2.name)) + required: + - rules + type: object + status: + description: Status defines the current state of UDPRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.x-k8s.io_xbackendtrafficpolicies.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + labels: + gateway.networking.k8s.io/policy: Direct + name: xbackendtrafficpolicies.gateway.networking.x-k8s.io +spec: + group: gateway.networking.x-k8s.io + names: + categories: + - gateway-api + kind: XBackendTrafficPolicy + listKind: XBackendTrafficPolicyList + plural: xbackendtrafficpolicies + shortNames: + - xbtrafficpolicy + singular: xbackendtrafficpolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + XBackendTrafficPolicy defines the configuration for how traffic to a + target backend should be handled. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of BackendTrafficPolicy. + properties: + retryConstraint: + description: |- + RetryConstraint defines the configuration for when to allow or prevent + further retries to a target backend, by dynamically calculating a 'retry + budget'. This budget is calculated based on the percentage of incoming + traffic composed of retries over a given time interval. Once the budget + is exceeded, additional retries will be rejected. + + For example, if the retry budget interval is 10 seconds, there have been + 1000 active requests in the past 10 seconds, and the allowed percentage + of requests that can be retried is 20% (the default), then 200 of those + requests may be composed of retries. Active requests will only be + considered for the duration of the interval when calculating the retry + budget. Retrying the same original request multiple times within the + retry budget interval will lead to each retry being counted towards + calculating the budget. + + Configuring a RetryConstraint in BackendTrafficPolicy is compatible with + HTTPRoute Retry settings for each HTTPRouteRule that targets the same + backend. While the HTTPRouteRule Retry stanza can specify whether a + request will be retried, and the number of retry attempts each client + may perform, RetryConstraint helps prevent cascading failures such as + retry storms during periods of consistent failures. + + After the retry budget has been exceeded, additional retries to the + backend MUST return a 503 response to the client. + + Additional configurations for defining a constraint on retries MAY be + defined in the future. + + Support: Extended + properties: + budget: + default: + interval: 10s + percent: 20 + description: Budget holds the details of the retry budget configuration. + properties: + interval: + default: 10s + description: |- + Interval defines the duration in which requests will be considered + for calculating the budget for retries. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + x-kubernetes-validations: + - message: interval can not be greater than one hour or less + than one second + rule: '!(duration(self) < duration(''1s'') || duration(self) + > duration(''1h''))' + percent: + default: 20 + description: |- + Percent defines the maximum percentage of active requests that may + be made up of retries. + + Support: Extended + maximum: 100 + minimum: 0 + type: integer + type: object + minRetryRate: + default: + count: 10 + interval: 1s + description: |- + MinRetryRate defines the minimum rate of retries that will be allowable + over a specified duration of time. + + The effective overall minimum rate of retries targeting the backend + service may be much higher, as there can be any number of clients which + are applying this setting locally. + + This ensures that requests can still be retried during periods of low + traffic, where the budget for retries may be calculated as a very low + value. + + Support: Extended + properties: + count: + description: |- + Count specifies the number of requests per time interval. + + Support: Extended + maximum: 1000000 + minimum: 1 + type: integer + interval: + description: |- + Interval specifies the divisor of the rate of requests, the amount of + time during which the given count of requests occur. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + x-kubernetes-validations: + - message: interval can not be greater than one hour + rule: '!(duration(self) == duration(''0s'') || duration(self) + > duration(''1h''))' + type: object + type: object + sessionPersistence: + description: |- + SessionPersistence defines and configures session persistence + for the backend. + + Support: Extended + properties: + absoluteTimeout: + description: |- + AbsoluteTimeout defines the absolute timeout of the persistent + session. Once the AbsoluteTimeout duration has elapsed, the + session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + cookieConfig: + description: |- + CookieConfig provides configuration settings that are specific + to cookie-based session persistence. + + Support: Core + properties: + lifetimeType: + default: Session + description: |- + LifetimeType specifies whether the cookie has a permanent or + session-based lifetime. A permanent cookie persists until its + specified expiry time, defined by the Expires or Max-Age cookie + attributes, while a session cookie is deleted when the current + session ends. + + When set to "Permanent", AbsoluteTimeout indicates the + cookie's lifetime via the Expires or Max-Age cookie attributes + and is required. + + When set to "Session", AbsoluteTimeout indicates the + absolute lifetime of the cookie tracked by the gateway and + is optional. + + Defaults to "Session". + + Support: Core for "Session" type + + Support: Extended for "Permanent" type + enum: + - Permanent + - Session + type: string + type: object + idleTimeout: + description: |- + IdleTimeout defines the idle timeout of the persistent session. + Once the session has been idle for more than the specified + IdleTimeout duration, the session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + sessionName: + description: |- + SessionName defines the name of the persistent session token + which may be reflected in the cookie or the header. Users + should avoid reusing session names to prevent unintended + consequences, such as rejection or unpredictable behavior. + + Support: Implementation-specific + maxLength: 128 + type: string + type: + default: Cookie + description: |- + Type defines the type of session persistence such as through + the use a header or cookie. Defaults to cookie based session + persistence. + + Support: Core for "Cookie" type + + Support: Extended for "Header" type + enum: + - Cookie + - Header + type: string + type: object + x-kubernetes-validations: + - message: AbsoluteTimeout must be specified when cookie lifetimeType + is Permanent + rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType) + || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)' + targetRefs: + description: |- + TargetRefs identifies API object(s) to apply this policy to. + Currently, Backends (A grouping of like endpoints such as Service, + ServiceImport, or any implementation-specific backendRef) are the only + valid API target references. + + Currently, a TargetRef can not be scoped to a specific port on a + Service. + items: + description: |- + LocalPolicyTargetReference identifies an API object to apply a direct or + inherited policy to. This should be used as part of Policy resources + that can target Gateway API resources. For more information on how this + policy attachment model works, and a sample Policy resource, refer to + the policy attachment documentation for Gateway API. + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - group + - kind + - name + x-kubernetes-list-type: map + required: + - targetRefs + type: object + status: + description: Status defines the current state of BackendTrafficPolicy. + properties: + ancestors: + description: |- + Ancestors is a list of ancestor resources (usually Gateways) that are + associated with the policy, and the status of the policy with respect to + each ancestor. When this policy attaches to a parent, the controller that + manages the parent and the ancestors MUST add an entry to this list when + the controller first sees the policy and SHOULD update the entry as + appropriate when the relevant ancestor is modified. + + Note that choosing the relevant ancestor is left to the Policy designers; + an important part of Policy design is designing the right object level at + which to namespace this status. + + Note also that implementations MUST ONLY populate ancestor status for + the Ancestor resources they are responsible for. Implementations MUST + use the ControllerName field to uniquely identify the entries in this list + that they are responsible for. + + Note that to achieve this, the list of PolicyAncestorStatus structs + MUST be treated as a map with a composite key, made up of the AncestorRef + and ControllerName fields combined. + + A maximum of 16 ancestors will be represented in this list. An empty list + means the Policy is not relevant for any ancestors. + + If this slice is full, implementations MUST NOT add further entries. + Instead they MUST consider the policy unimplementable and signal that + on any related resources such as the ancestor that would be referenced + here. For example, if this list was full on BackendTLSPolicy, no + additional Gateways would be able to reference the Service targeted by + the BackendTLSPolicy. + items: + description: |- + PolicyAncestorStatus describes the status of a route with respect to an + associated Ancestor. + + Ancestors refer to objects that are either the Target of a policy or above it + in terms of object hierarchy. For example, if a policy targets a Service, the + Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and + the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most + useful object to place Policy status on, so we recommend that implementations + SHOULD use Gateway as the PolicyAncestorStatus object unless the designers + have a _very_ good reason otherwise. + + In the context of policy attachment, the Ancestor is used to distinguish which + resource results in a distinct application of this policy. For example, if a policy + targets a Service, it may have a distinct result per attached Gateway. + + Policies targeting the same resource may have different effects depending on the + ancestors of those resources. For example, different Gateways targeting the same + Service may have different capabilities, especially if they have different underlying + implementations. + + For example, in BackendTLSPolicy, the Policy attaches to a Service that is + used as a backend in a HTTPRoute that is itself attached to a Gateway. + In this case, the relevant object for status is the Gateway, and that is the + ancestor object referred to in this status. + + Note that a parent is also an ancestor, so for objects where the parent is the + relevant object for status, this struct SHOULD still be used. + + This struct is intended to be used in a slice that's effectively a map, + with a composite key made up of the AncestorRef and the ControllerName. + properties: + ancestorRef: + description: |- + AncestorRef corresponds with a ParentRef in the spec that this + PolicyAncestorStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: Conditions describes the status of the Policy with + respect to the given Ancestor. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - controllerName + type: object + maxItems: 16 + type: array + required: + - ancestors + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.x-k8s.io_xlistenersets.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: xlistenersets.gateway.networking.x-k8s.io +spec: + group: gateway.networking.x-k8s.io + names: + categories: + - gateway-api + kind: XListenerSet + listKind: XListenerSetList + plural: xlistenersets + shortNames: + - lset + singular: xlistenerset + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + XListenerSet defines a set of additional listeners + to attach to an existing Gateway. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of ListenerSet. + properties: + listeners: + description: |- + Listeners associated with this ListenerSet. Listeners define + logical endpoints that are bound on this referenced parent Gateway's addresses. + + Listeners in a `Gateway` and their attached `ListenerSets` are concatenated + as a list when programming the underlying infrastructure. Each listener + name does not need to be unique across the Gateway and ListenerSets. + See ListenerEntry.Name for more details. + + Implementations MUST treat the parent Gateway as having the merged + list of all listeners from itself and attached ListenerSets using + the following precedence: + + 1. "parent" Gateway + 2. ListenerSet ordered by creation time (oldest first) + 3. ListenerSet ordered alphabetically by “{namespace}/{name}”. + + An implementation MAY reject listeners by setting the ListenerEntryStatus + `Accepted`` condition to False with the Reason `TooManyListeners` + + If a listener has a conflict, this will be reported in the + Status.ListenerEntryStatus setting the `Conflicted` condition to True. + + Implementations SHOULD be cautious about what information from the + parent or siblings are reported to avoid accidentally leaking + sensitive information that the child would not otherwise have access + to. This can include contents of secrets etc. + items: + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: |- + AllowedRoutes defines the types of routes that MAY be attached to a + Listener and the trusted namespaces where those Route resources MAY be + present. + + Although a client request may match multiple route rules, only one rule + may ultimately receive the request. Matching precedence MUST be + determined in order of the following criteria: + + * The most specific match as defined by the Route type. + * The oldest Route based on creation timestamp. For example, a Route with + a creation timestamp of "2020-09-08 01:02:03" is given precedence over + a Route with a creation timestamp of "2020-09-08 01:02:04". + * If everything else is equivalent, the Route appearing first in + alphabetical order (namespace/name) should be given precedence. For + example, foo/bar is given precedence over foo/baz. + + All valid rules within a Route attached to this Listener should be + implemented. Invalid Route rules can be ignored (sometimes that will mean + the full Route). If a Route rule transitions from valid to invalid, + support for that Route rule should be dropped to ensure consistency. For + example, even if a filter specified by a Route rule is invalid, the rest + of the rules within that Route should still be supported. + properties: + kinds: + description: |- + Kinds specifies the groups and kinds of Routes that are allowed to bind + to this Gateway Listener. When unspecified or empty, the kinds of Routes + selected are determined using the Listener protocol. + + A RouteGroupKind MUST correspond to kinds of Routes that are compatible + with the application protocol specified in the Listener's Protocol field. + If an implementation does not support or recognize this resource type, it + MUST set the "ResolvedRefs" condition to False for this Listener with the + "InvalidRouteKinds" reason. + + Support: Core + items: + description: RouteGroupKind indicates the group and kind + of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: |- + Namespaces indicates namespaces from which Routes may be attached to this + Listener. This is restricted to the namespace of this Gateway by default. + + Support: Core + properties: + from: + default: Same + description: |- + From indicates where Routes will be selected for this Gateway. Possible + values are: + + * All: Routes in all namespaces may be used by this Gateway. + * Selector: Routes in namespaces selected by the selector may be used by + this Gateway. + * Same: Only Routes in the same namespace may be used by this Gateway. + + Support: Core + enum: + - All + - Selector + - Same + type: string + selector: + description: |- + Selector must be specified when From is set to "Selector". In that case, + only Routes in Namespaces matching this Selector will be selected by this + Gateway. This field is ignored for other values of "From". + + Support: Core + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + type: object + type: object + hostname: + description: |- + Hostname specifies the virtual hostname to match for protocol types that + define this concept. When unspecified, all hostnames are matched. This + field is ignored for protocols that don't require hostname based + matching. + + Implementations MUST apply Hostname matching appropriately for each of + the following protocols: + + * TLS: The Listener Hostname MUST match the SNI. + * HTTP: The Listener Hostname MUST match the Host header of the request. + * HTTPS: The Listener Hostname SHOULD match at both the TLS and HTTP + protocol layers as described above. If an implementation does not + ensure that both the SNI and Host header match the Listener hostname, + it MUST clearly document that. + + For HTTPRoute and TLSRoute resources, there is an interaction with the + `spec.hostnames` array. When both listener and route specify hostnames, + there MUST be an intersection between the values for a Route to be + accepted. For more information, refer to the Route specific Hostnames + documentation. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + name: + description: |- + Name is the name of the Listener. This name MUST be unique within a + ListenerSet. + + Name is not required to be unique across a Gateway and ListenerSets. + Routes can attach to a Listener by having a ListenerSet as a parentRef + and setting the SectionName + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: |- + Port is the network port. Multiple listeners may use the + same port, subject to the Listener compatibility rules. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: Protocol specifies the network protocol this listener + expects to receive. + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: |- + TLS is the TLS configuration for the Listener. This field is required if + the Protocol field is "HTTPS" or "TLS". It is invalid to set this field + if the Protocol field is "HTTP", "TCP", or "UDP". + + The association of SNIs to Certificate defined in GatewayTLSConfig is + defined based on the Hostname field for this listener. + + The GatewayClass MUST use the longest matching SNI out of all + available certificates for any TLS handshake. + properties: + certificateRefs: + description: |- + CertificateRefs contains a series of references to Kubernetes objects that + contains TLS certificates and private keys. These certificates are used to + establish a TLS handshake for requests that match the hostname of the + associated listener. + + A single CertificateRef to a Kubernetes Secret has "Core" support. + Implementations MAY choose to support attaching multiple certificates to + a Listener, but this behavior is implementation-specific. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + + This field is required to have at least one element when the mode is set + to "Terminate" (default) and is optional otherwise. + + CertificateRefs can reference to standard Kubernetes resources, i.e. + Secret, or implementation-specific custom resources. + + Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls + + Support: Implementation-specific (More than one reference or other resource types) + items: + description: |- + SecretObjectReference identifies an API object including its namespace, + defaulting to Secret. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example + "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + frontendValidation: + description: |- + FrontendValidation holds configuration information for validating the frontend (client). + Setting this field will require clients to send a client certificate + required for validation during the TLS handshake. In browsers this may result in a dialog appearing + that requests a user to specify the client certificate. + The maximum depth of a certificate chain accepted in verification is Implementation specific. + + Support: Extended + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to + Kubernetes objects that contain TLS certificates of + the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the client. + + A single CA certificate reference to a Kubernetes ConfigMap + has "Core" support. + Implementations MAY choose to support attaching multiple CA certificates to + a Listener, but this behavior is implementation-specific. + + Support: Core - A single reference to a Kubernetes ConfigMap + with the CA certificate in a key named `ca.crt`. + + Support: Implementation-specific (More than one reference, or other kinds + of resources). + + References to a resource in a different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + items: + description: |- + ObjectReference identifies an API object including its namespace. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When set to the empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "ConfigMap" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + maxItems: 8 + minItems: 1 + type: array + type: object + mode: + default: Terminate + description: |- + Mode defines the TLS behavior for the TLS session initiated by the client. + There are two possible modes: + + - Terminate: The TLS session between the downstream client and the + Gateway is terminated at the Gateway. This mode requires certificates + to be specified in some way, such as populating the certificateRefs + field. + - Passthrough: The TLS session is NOT terminated by the Gateway. This + implies that the Gateway can't decipher the TLS stream except for + the ClientHello message of the TLS protocol. The certificateRefs field + is ignored in this mode. + + Support: Core + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Options are a list of key/value pairs to enable extended TLS + configuration for each implementation. For example, configuring the + minimum TLS version or supported cipher suites. + + A set of common keys MAY be defined by the API in the future. To avoid + any ambiguity, implementation-specific definitions MUST use + domain-prefixed names, such as `example.com/my-custom-option`. + Un-prefixed names are reserved for key names defined by Gateway API. + + Support: Implementation-specific + maxProperties: 16 + type: object + type: object + x-kubernetes-validations: + - message: certificateRefs or options must be specified when + mode is Terminate + rule: 'self.mode == ''Terminate'' ? size(self.certificateRefs) + > 0 || size(self.options) > 0 : true' + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: tls must not be specified for protocols ['HTTP', 'TCP', + 'UDP'] + rule: 'self.all(l, l.protocol in [''HTTP'', ''TCP'', ''UDP''] ? + !has(l.tls) : true)' + - message: tls mode must be Terminate for protocol HTTPS + rule: 'self.all(l, (l.protocol == ''HTTPS'' && has(l.tls)) ? (l.tls.mode + == '''' || l.tls.mode == ''Terminate'') : true)' + - message: hostname must not be specified for protocols ['TCP', 'UDP'] + rule: 'self.all(l, l.protocol in [''TCP'', ''UDP''] ? (!has(l.hostname) + || l.hostname == '''') : true)' + - message: Listener name must be unique within the Gateway + rule: self.all(l1, self.exists_one(l2, l1.name == l2.name)) + - message: Combination of port, protocol and hostname must be unique + for each listener + rule: 'self.all(l1, !has(l1.port) || self.exists_one(l2, has(l2.port) + && l1.port == l2.port && l1.protocol == l2.protocol && (has(l1.hostname) + && has(l2.hostname) ? l1.hostname == l2.hostname : !has(l1.hostname) + && !has(l2.hostname))))' + parentRef: + description: ParentRef references the Gateway that the listeners are + attached to. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: Kind is kind of the referent. For example "Gateway". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. If not present, + the namespace of the referent is assumed to be the same as + the namespace of the referring object. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + required: + - listeners + - parentRef + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: Status defines the current state of ListenerSet. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: |- + Conditions describe the current conditions of the ListenerSet. + + Implementations MUST express ListenerSet conditions using the + `ListenerSetConditionType` and `ListenerSetConditionReason` + constants so that operators and tools can converge on a common + vocabulary to describe ListenerSet state. + + Known condition types are: + + * "Accepted" + * "Programmed" + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port + defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: |- + AttachedRoutes represents the total number of Routes that have been + successfully attached to this Listener. + + Successful attachment of a Route to a Listener is based solely on the + combination of the AllowedRoutes field on the corresponding Listener + and the Route's ParentRefs field. A Route is successfully attached to + a Listener when it is selected by the Listener's AllowedRoutes field + AND the Route has a valid ParentRef selecting the whole Gateway + resource or a specific Listener as a parent resource (more detail on + attachment semantics can be found in the documentation on the various + Route kinds ParentRefs fields). Listener or Route status does not impact + successful attachment, i.e. the AttachedRoutes field count MUST be set + for Listeners with condition Accepted: false and MUST count successfully + attached Routes that may themselves have Accepted: false conditions. + + Uses for this field include troubleshooting Route attachment and + measuring blast radius/impact of changes to a Listener. + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this + listener. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status + corresponds to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: Port is the network port the listener is configured + to listen on. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + supportedKinds: + description: |- + SupportedKinds is the list indicating the Kinds supported by this + listener. This MUST represent the kinds an implementation supports for + that Listener configuration. + + If kinds are specified in Spec that are not supported, they MUST NOT + appear in this list and an implementation MUST set the "ResolvedRefs" + condition to "False" with the "InvalidRouteKinds" reason. If both valid + and invalid Route kinds are specified, the implementation MUST + reference the valid Route kinds that have been specified. + items: + description: RouteGroupKind indicates the group and kind of + a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - port + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backends.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 + name: backends.gateway.envoyproxy.io +spec: + group: gateway.envoyproxy.io + names: + categories: + - envoy-gateway + kind: Backend + listKind: BackendList + plural: backends + shortNames: + - be + singular: backend + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Accepted")].reason + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + Backend allows the user to configure the endpoints of a backend and + the behavior of the connection from Envoy Proxy to the backend. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of Backend. + properties: + appProtocols: + description: AppProtocols defines the application protocols to be + supported when connecting to the backend. + items: + description: AppProtocolType defines various backend applications + protocols supported by Envoy Gateway + enum: + - gateway.envoyproxy.io/h2c + - gateway.envoyproxy.io/ws + - gateway.envoyproxy.io/wss + type: string + type: array + endpoints: + description: Endpoints defines the endpoints to be used when connecting + to the backend. + items: + description: |- + BackendEndpoint describes a backend endpoint, which can be either a fully-qualified domain name, IP address or unix domain socket + corresponding to Envoy's Address: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto#config-core-v3-address + properties: + fqdn: + description: FQDN defines a FQDN endpoint + properties: + hostname: + description: Hostname defines the FQDN hostname of the backend + endpoint. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: Port defines the port of the backend endpoint. + format: int32 + maximum: 65535 + minimum: 0 + type: integer + required: + - hostname + - port + type: object + ip: + description: IP defines an IP endpoint. Supports both IPv4 and + IPv6 addresses. + properties: + address: + description: |- + Address defines the IP address of the backend endpoint. + Supports both IPv4 and IPv6 addresses. + maxLength: 45 + minLength: 3 + pattern: ^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|^(([0-9a-fA-F]{1,4}:){1,7}[0-9a-fA-F]{1,4}|::|(([0-9a-fA-F]{1,4}:){0,5})?(:[0-9a-fA-F]{1,4}){1,2})$ + type: string + port: + description: Port defines the port of the backend endpoint. + format: int32 + maximum: 65535 + minimum: 0 + type: integer + required: + - address + - port + type: object + unix: + description: Unix defines the unix domain socket endpoint + properties: + path: + description: Path defines the unix domain socket path of + the backend endpoint. + type: string + required: + - path + type: object + type: object + x-kubernetes-validations: + - message: one of fqdn, ip or unix must be specified + rule: (has(self.fqdn) || has(self.ip) || has(self.unix)) + - message: only one of fqdn, ip or unix can be specified + rule: ((has(self.fqdn) && !(has(self.ip) || has(self.unix))) || + (has(self.ip) && !(has(self.fqdn) || has(self.unix))) || (has(self.unix) + && !(has(self.ip) || has(self.fqdn)))) + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-validations: + - message: fqdn addresses cannot be mixed with other address types + rule: self.all(f, has(f.fqdn)) || !self.exists(f, has(f.fqdn)) + fallback: + description: |- + Fallback indicates whether the backend is designated as a fallback. + It is highly recommended to configure active or passive health checks to ensure that failover can be detected + when the active backends become unhealthy and to automatically readjust once the primary backends are healthy again. + The overprovisioning factor is set to 1.4, meaning the fallback backends will only start receiving traffic when + the health of the active backends falls below 72%. + type: boolean + tls: + description: |- + TLS defines the TLS settings for the backend. + Only supported for DynamicResolver backends. + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to Kubernetes objects that + contain TLS certificates of the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the backend. + + A single reference to a Kubernetes ConfigMap or a Kubernetes Secret, + with the CA certificate in a key named `ca.crt` is currently supported. + + If CACertificateRefs is empty or unspecified, then WellKnownCACertificates must be + specified. Only one of CACertificateRefs or WellKnownCACertificates may be specified, + not both. + items: + description: |- + LocalObjectReference identifies an API object within the namespace of the + referrer. + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" + or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + maxItems: 8 + type: array + wellKnownCACertificates: + description: |- + WellKnownCACertificates specifies whether system CA certificates may be used in + the TLS handshake between the gateway and backend pod. + + If WellKnownCACertificates is unspecified or empty (""), then CACertificateRefs + must be specified with at least one entry for a valid configuration. Only one of + CACertificateRefs or WellKnownCACertificates may be specified, not both. + enum: + - System + type: string + type: object + type: + default: Endpoints + description: Type defines the type of the backend. Defaults to "Endpoints" + enum: + - Endpoints + - DynamicResolver + type: string + type: object + x-kubernetes-validations: + - message: DynamicResolver type cannot have endpoints and appProtocols + specified + rule: self.type != 'DynamicResolver' || !has(self.endpoints) && !has(self.appProtocols) + status: + description: Status defines the current status of Backend. + properties: + conditions: + description: Conditions describe the current conditions of the Backend. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 + name: backendtrafficpolicies.gateway.envoyproxy.io +spec: + group: gateway.envoyproxy.io + names: + categories: + - envoy-gateway + kind: BackendTrafficPolicy + listKind: BackendTrafficPolicyList + plural: backendtrafficpolicies + shortNames: + - btp + singular: backendtrafficpolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + BackendTrafficPolicy allows the user to configure the behavior of the connection + between the Envoy Proxy listener and the backend service. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec defines the desired state of BackendTrafficPolicy. + properties: + circuitBreaker: + description: |- + Circuit Breaker settings for the upstream connections and requests. + If not set, circuit breakers will be enabled with the default thresholds + properties: + maxConnections: + default: 1024 + description: The maximum number of connections that Envoy will + establish to the referenced backend defined within a xRoute + rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRequests: + default: 1024 + description: The maximum number of parallel requests that Envoy + will make to the referenced backend defined within a xRoute + rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRetries: + default: 1024 + description: The maximum number of parallel retries that Envoy + will make to the referenced backend defined within a xRoute + rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxPendingRequests: + default: 1024 + description: The maximum number of pending requests that Envoy + will queue to the referenced backend defined within a xRoute + rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxRequestsPerConnection: + description: |- + The maximum number of requests that Envoy will make over a single connection to the referenced backend defined within a xRoute rule. + Default: unlimited. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + perEndpoint: + description: PerEndpoint defines Circuit Breakers that will apply + per-endpoint for an upstream cluster + properties: + maxConnections: + default: 1024 + description: MaxConnections configures the maximum number + of connections that Envoy will establish per-endpoint to + the referenced backend defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + type: object + type: object + compression: + description: The compression config for the http streams. + items: + description: |- + Compression defines the config of enabling compression. + This can help reduce the bandwidth at the expense of higher CPU. + properties: + brotli: + description: The configuration for Brotli compressor. + type: object + gzip: + description: The configuration for GZIP compressor. + type: object + type: + description: CompressorType defines the compressor type to use + for compression. + enum: + - Gzip + - Brotli + type: string + required: + - type + type: object + type: array + connection: + description: Connection includes backend connection settings. + properties: + bufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + BufferLimit Soft limit on size of the cluster’s connections read and write buffers. + BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space. + If unspecified, an implementation defined default is applied (32768 bytes). + For example, 20Mi, 1Gi, 256Ki etc. + Note: that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + socketBufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + SocketBufferLimit provides configuration for the maximum buffer size in bytes for each socket + to backend. + SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space. + For example, 20Mi, 1Gi, 256Ki etc. + Note that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + type: object + dns: + description: DNS includes dns resolution settings. + properties: + dnsRefreshRate: + description: |- + DNSRefreshRate specifies the rate at which DNS records should be refreshed. + Defaults to 30 seconds. + type: string + lookupFamily: + description: |- + LookupFamily determines how Envoy would resolve DNS for Routes where the backend is specified as a fully qualified domain name (FQDN). + If set, this configuration overrides other defaults. + enum: + - IPv4 + - IPv6 + - IPv4Preferred + - IPv6Preferred + - IPv4AndIPv6 + type: string + respectDnsTtl: + description: |- + RespectDNSTTL indicates whether the DNS Time-To-Live (TTL) should be respected. + If the value is set to true, the DNS refresh rate will be set to the resource record’s TTL. + Defaults to true. + type: boolean + type: object + faultInjection: + description: |- + FaultInjection defines the fault injection policy to be applied. This configuration can be used to + inject delays and abort requests to mimic failure scenarios such as service failures and overloads + properties: + abort: + description: If specified, the request will be aborted if it meets + the configuration criteria. + properties: + grpcStatus: + description: GrpcStatus specifies the GRPC status code to + be returned + format: int32 + type: integer + httpStatus: + description: StatusCode specifies the HTTP status code to + be returned + format: int32 + maximum: 600 + minimum: 200 + type: integer + percentage: + default: 100 + description: Percentage specifies the percentage of requests + to be aborted. Default 100%, if set 0, no requests will + be aborted. Accuracy to 0.0001%. + type: number + type: object + x-kubernetes-validations: + - message: httpStatus and grpcStatus cannot be simultaneously + defined. + rule: ' !(has(self.httpStatus) && has(self.grpcStatus)) ' + - message: httpStatus and grpcStatus are set at least one. + rule: ' has(self.httpStatus) || has(self.grpcStatus) ' + delay: + description: If specified, a delay will be injected into the request. + properties: + fixedDelay: + description: FixedDelay specifies the fixed delay duration + type: string + percentage: + default: 100 + description: Percentage specifies the percentage of requests + to be delayed. Default 100%, if set 0, no requests will + be delayed. Accuracy to 0.0001%. + type: number + required: + - fixedDelay + type: object + type: object + x-kubernetes-validations: + - message: Delay and abort faults are set at least one. + rule: ' has(self.delay) || has(self.abort) ' + healthCheck: + description: HealthCheck allows gateway to perform active health checking + on backends. + properties: + active: + description: Active health check configuration + properties: + grpc: + description: |- + GRPC defines the configuration of the GRPC health checker. + It's optional, and can only be used if the specified type is GRPC. + properties: + service: + description: |- + Service to send in the health check request. + If this is not specified, then the health check request applies to the entire + server and not to a specific service. + type: string + type: object + healthyThreshold: + default: 1 + description: HealthyThreshold defines the number of healthy + health checks required before a backend host is marked healthy. + format: int32 + minimum: 1 + type: integer + http: + description: |- + HTTP defines the configuration of http health checker. + It's required while the health checker type is HTTP. + properties: + expectedResponse: + description: ExpectedResponse defines a list of HTTP expected + responses to match. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field needs to + be set. + rule: 'self.type == ''Text'' ? has(self.text) : !has(self.text)' + - message: If payload type is Binary, binary field needs + to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) : + !has(self.binary)' + expectedStatuses: + description: |- + ExpectedStatuses defines a list of HTTP response statuses considered healthy. + Defaults to 200 only + items: + description: HTTPStatus defines the http status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + method: + description: |- + Method defines the HTTP method used for health checking. + Defaults to GET + type: string + path: + description: Path defines the HTTP path that will be requested + during health checking. + maxLength: 1024 + minLength: 1 + type: string + required: + - path + type: object + interval: + default: 3s + description: Interval defines the time between active health + checks. + format: duration + type: string + tcp: + description: |- + TCP defines the configuration of tcp health checker. + It's required while the health checker type is TCP. + properties: + receive: + description: Receive defines the expected response payload. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field needs to + be set. + rule: 'self.type == ''Text'' ? has(self.text) : !has(self.text)' + - message: If payload type is Binary, binary field needs + to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) : + !has(self.binary)' + send: + description: Send defines the request payload. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field needs to + be set. + rule: 'self.type == ''Text'' ? has(self.text) : !has(self.text)' + - message: If payload type is Binary, binary field needs + to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) : + !has(self.binary)' + type: object + timeout: + default: 1s + description: Timeout defines the time to wait for a health + check response. + format: duration + type: string + type: + allOf: + - enum: + - HTTP + - TCP + - GRPC + - enum: + - HTTP + - TCP + - GRPC + description: Type defines the type of health checker. + type: string + unhealthyThreshold: + default: 3 + description: UnhealthyThreshold defines the number of unhealthy + health checks required before a backend host is marked unhealthy. + format: int32 + minimum: 1 + type: integer + required: + - type + type: object + x-kubernetes-validations: + - message: If Health Checker type is HTTP, http field needs to + be set. + rule: 'self.type == ''HTTP'' ? has(self.http) : !has(self.http)' + - message: If Health Checker type is TCP, tcp field needs to be + set. + rule: 'self.type == ''TCP'' ? has(self.tcp) : !has(self.tcp)' + - message: The grpc field can only be set if the Health Checker + type is GRPC. + rule: 'has(self.grpc) ? self.type == ''GRPC'' : true' + panicThreshold: + description: |- + When number of unhealthy endpoints for a backend reaches this threshold + Envoy will disregard health status and balance across all endpoints. + It's designed to prevent a situation in which host failures cascade throughout the cluster + as load increases. If not set, the default value is 50%. To disable panic mode, set value to `0`. + format: int32 + maximum: 100 + minimum: 0 + type: integer + passive: + description: Passive passive check configuration + properties: + baseEjectionTime: + default: 30s + description: BaseEjectionTime defines the base duration for + which a host will be ejected on consecutive failures. + format: duration + type: string + consecutive5XxErrors: + default: 5 + description: Consecutive5xxErrors sets the number of consecutive + 5xx errors triggering ejection. + format: int32 + type: integer + consecutiveGatewayErrors: + default: 0 + description: ConsecutiveGatewayErrors sets the number of consecutive + gateway errors triggering ejection. + format: int32 + type: integer + consecutiveLocalOriginFailures: + default: 5 + description: |- + ConsecutiveLocalOriginFailures sets the number of consecutive local origin failures triggering ejection. + Parameter takes effect only when split_external_local_origin_errors is set to true. + format: int32 + type: integer + interval: + default: 3s + description: Interval defines the time between passive health + checks. + format: duration + type: string + maxEjectionPercent: + default: 10 + description: MaxEjectionPercent sets the maximum percentage + of hosts in a cluster that can be ejected. + format: int32 + type: integer + splitExternalLocalOriginErrors: + default: false + description: SplitExternalLocalOriginErrors enables splitting + of errors between external and local origin. + type: boolean + type: object + type: object + http2: + description: HTTP2 provides HTTP/2 configuration for backend connections. + properties: + initialConnectionWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialConnectionWindowSize sets the initial window size for HTTP/2 connections. + If not set, the default value is 1 MiB. + x-kubernetes-int-or-string: true + initialStreamWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialStreamWindowSize sets the initial window size for HTTP/2 streams. + If not set, the default value is 64 KiB(64*1024). + x-kubernetes-int-or-string: true + maxConcurrentStreams: + description: |- + MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection. + If not set, the default value is 100. + format: int32 + maximum: 2147483647 + minimum: 1 + type: integer + onInvalidMessage: + description: |- + OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error + It's recommended for L2 Envoy deployments to set this value to TerminateStream. + https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two + Default: TerminateConnection + type: string + type: object + httpUpgrade: + description: |- + HTTPUpgrade defines the configuration for HTTP protocol upgrades. + If not specified, the default upgrade configuration(websocket) will be used. + items: + properties: + type: + description: |- + Type is the case-insensitive type of protocol upgrade. + e.g. `websocket`, `CONNECT`, `spdy/3.1` etc. + type: string + required: + - type + type: object + type: array + loadBalancer: + description: |- + LoadBalancer policy to apply when routing traffic from the gateway to + the backend endpoints. Defaults to `LeastRequest`. + properties: + consistentHash: + description: |- + ConsistentHash defines the configuration when the load balancer type is + set to ConsistentHash + properties: + cookie: + description: Cookie configures the cookie hash policy when + the consistent hash type is set to Cookie. + properties: + attributes: + additionalProperties: + type: string + description: Additional Attributes to set for the generated + cookie. + type: object + name: + description: |- + Name of the cookie to hash. + If this cookie does not exist in the request, Envoy will generate a cookie and set + the TTL on the response back to the client based on Layer 4 + attributes of the backend endpoint, to ensure that these future requests + go to the same backend endpoint. Make sure to set the TTL field for this case. + type: string + ttl: + description: |- + TTL of the generated cookie if the cookie is not present. This value sets the + Max-Age attribute value. + type: string + required: + - name + type: object + header: + description: Header configures the header hash policy when + the consistent hash type is set to Header. + properties: + name: + description: Name of the header to hash. + type: string + required: + - name + type: object + tableSize: + default: 65537 + description: The table size for consistent hashing, must be + prime number limited to 5000011. + format: int64 + maximum: 5000011 + minimum: 2 + type: integer + type: + description: |- + ConsistentHashType defines the type of input to hash on. Valid Type values are + "SourceIP", + "Header", + "Cookie". + enum: + - SourceIP + - Header + - Cookie + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If consistent hash type is header, the header field + must be set. + rule: 'self.type == ''Header'' ? has(self.header) : !has(self.header)' + - message: If consistent hash type is cookie, the cookie field + must be set. + rule: 'self.type == ''Cookie'' ? has(self.cookie) : !has(self.cookie)' + slowStart: + description: |- + SlowStart defines the configuration related to the slow start load balancer policy. + If set, during slow start window, traffic sent to the newly added hosts will gradually increase. + Currently this is only supported for RoundRobin and LeastRequest load balancers + properties: + window: + description: |- + Window defines the duration of the warm up period for newly added host. + During slow start window, traffic sent to the newly added hosts will gradually increase. + Currently only supports linear growth of traffic. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#config-cluster-v3-cluster-slowstartconfig + type: string + required: + - window + type: object + type: + description: |- + Type decides the type of Load Balancer policy. + Valid LoadBalancerType values are + "ConsistentHash", + "LeastRequest", + "Random", + "RoundRobin". + enum: + - ConsistentHash + - LeastRequest + - Random + - RoundRobin + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If LoadBalancer type is consistentHash, consistentHash + field needs to be set. + rule: 'self.type == ''ConsistentHash'' ? has(self.consistentHash) + : !has(self.consistentHash)' + - message: Currently SlowStart is only supported for RoundRobin and + LeastRequest load balancers. + rule: 'self.type in [''Random'', ''ConsistentHash''] ? !has(self.slowStart) + : true ' + mergeType: + description: |- + MergeType determines how this configuration is merged with existing BackendTrafficPolicy + configurations targeting a parent resource. When set, this configuration will be merged + into a parent BackendTrafficPolicy (i.e. the one targeting a Gateway or Listener). + This field cannot be set when targeting a parent resource (Gateway). + If unset, no merging occurs, and only the most specific configuration takes effect. + type: string + proxyProtocol: + description: ProxyProtocol enables the Proxy Protocol when communicating + with the backend. + properties: + version: + description: |- + Version of ProxyProtol + Valid ProxyProtocolVersion values are + "V1" + "V2" + enum: + - V1 + - V2 + type: string + required: + - version + type: object + rateLimit: + description: |- + RateLimit allows the user to limit the number of incoming requests + to a predefined value based on attributes within the traffic flow. + properties: + global: + description: Global defines global rate limit configuration. + properties: + rules: + description: |- + Rules are a list of RateLimit selectors and limits. Each rule and its + associated limit is applied in a mutually exclusive way. If a request + matches multiple rules, each of their associated limits get applied, so a + single request might increase the rate limit counters for multiple rules + if selected. The rate limit service will return a logical OR of the individual + rate limit decisions of all matching rules. For example, if a request + matches two rules, one rate limited and one not, the final decision will be + to rate limit the request. + items: + description: |- + RateLimitRule defines the semantics for matching attributes + from the incoming requests, and setting limits for them. + properties: + clientSelectors: + description: |- + 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 no client selectors are specified, the rule applies to all traffic of + the targeted Route. + + If the policy targets a Gateway, the rule applies to each Route of the Gateway. + Please note that each Route has its own rate limit counters. For example, + if a Gateway has two Routes, and the policy has a rule with limit 10rps, + each Route will have its own 10rps limit. + items: + description: |- + 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. + properties: + headers: + description: |- + Headers is a list of request headers to match. Multiple header values are ANDed together, + meaning, a request MUST match all the specified headers. + At least one of headers or sourceCIDR condition must be specified. + items: + description: HeaderMatch defines the match attributes + within the HTTP Headers of the request. + properties: + invert: + default: false + description: |- + Invert specifies whether the value match result will be inverted. + Do not set this field when Type="Distinct", implying matching on any/all unique + values within the header. + type: boolean + name: + description: |- + Name of the HTTP header. + The header name is case-insensitive unless PreserveHeaderCase is set to true. + For example, "Foo" and "foo" are considered the same header. + maxLength: 256 + minLength: 1 + type: string + type: + default: Exact + description: Type specifies how to match + against the value of the header. + enum: + - Exact + - RegularExpression + - Distinct + type: string + value: + description: |- + Value within the HTTP header. + Do not set this field when Type="Distinct", implying matching on any/all unique + values within the header. + maxLength: 1024 + type: string + required: + - name + type: object + maxItems: 16 + type: array + sourceCIDR: + description: |- + SourceCIDR is the client IP Address range to match on. + At least one of headers or sourceCIDR condition must be specified. + properties: + type: + default: Exact + enum: + - Exact + - Distinct + type: string + value: + description: |- + Value 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`. + maxLength: 256 + minLength: 1 + type: string + required: + - value + type: object + type: object + maxItems: 8 + type: array + cost: + description: |- + Cost specifies the cost of requests and responses for the rule. + + This is optional and if not specified, the default behavior is to reduce the rate limit counters by 1 on + the request path and do not reduce the rate limit counters on the response path. + properties: + request: + description: |- + Request specifies the number to reduce the rate limit counters + on the request path. If this is not specified, the default behavior + is to reduce the rate limit counters by 1. + + When Envoy receives a request that matches the rule, it tries to reduce the + rate limit counters by the specified number. If the counter doesn't have + enough capacity, the request is rate limited. + properties: + from: + description: From specifies where to get the + rate limit cost. Currently, only "Number" + and "Metadata" are supported. + enum: + - Number + - Metadata + type: string + metadata: + description: Metadata specifies the per-request + metadata to retrieve the usage number from. + properties: + key: + description: Key is the key to retrieve + the usage number from the filter metadata. + type: string + namespace: + description: Namespace is the namespace + of the dynamic metadata. + type: string + required: + - key + - namespace + type: object + number: + description: |- + Number specifies the fixed usage number to reduce the rate limit counters. + Using zero can be used to only check the rate limit counters without reducing them. + format: int64 + type: integer + required: + - from + type: object + x-kubernetes-validations: + - message: only one of number or metadata can be + specified + rule: '!(has(self.number) && has(self.metadata))' + response: + description: |- + Response specifies the number to reduce the rate limit counters + after the response is sent back to the client or the request stream is closed. + + The cost is used to reduce the rate limit counters for the matching requests. + Since the reduction happens after the request stream is complete, the rate limit + won't be enforced for the current request, but for the subsequent matching requests. + + This is optional and if not specified, the rate limit counters are not reduced + on the response path. + + Currently, this is only supported for HTTP Global Rate Limits. + properties: + from: + description: From specifies where to get the + rate limit cost. Currently, only "Number" + and "Metadata" are supported. + enum: + - Number + - Metadata + type: string + metadata: + description: Metadata specifies the per-request + metadata to retrieve the usage number from. + properties: + key: + description: Key is the key to retrieve + the usage number from the filter metadata. + type: string + namespace: + description: Namespace is the namespace + of the dynamic metadata. + type: string + required: + - key + - namespace + type: object + number: + description: |- + Number specifies the fixed usage number to reduce the rate limit counters. + Using zero can be used to only check the rate limit counters without reducing them. + format: int64 + type: integer + required: + - from + type: object + x-kubernetes-validations: + - message: only one of number or metadata can be + specified + rule: '!(has(self.number) && has(self.metadata))' + type: object + limit: + description: |- + 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. + properties: + requests: + type: integer + unit: + description: |- + RateLimitUnit specifies the intervals for setting rate limits. + Valid RateLimitUnit values are "Second", "Minute", "Hour", and "Day". + enum: + - Second + - Minute + - Hour + - Day + type: string + required: + - requests + - unit + type: object + required: + - limit + type: object + maxItems: 64 + type: array + shared: + default: false + description: |- + Shared determines whether the rate limit rules apply across all the policy targets. + If set to true, the rule is treated as a common bucket and is shared across all policy targets (xRoutes). + Default: false. + type: boolean + required: + - rules + type: object + local: + description: Local defines local rate limit configuration. + properties: + rules: + description: |- + Rules are a list of RateLimit selectors and limits. If a request matches + multiple rules, the strictest limit is applied. For example, if a request + matches two rules, one with 10rps and one with 20rps, the final limit will + be based on the rule with 10rps. + items: + description: |- + RateLimitRule defines the semantics for matching attributes + from the incoming requests, and setting limits for them. + properties: + clientSelectors: + description: |- + 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 no client selectors are specified, the rule applies to all traffic of + the targeted Route. + + If the policy targets a Gateway, the rule applies to each Route of the Gateway. + Please note that each Route has its own rate limit counters. For example, + if a Gateway has two Routes, and the policy has a rule with limit 10rps, + each Route will have its own 10rps limit. + items: + description: |- + 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. + properties: + headers: + description: |- + Headers is a list of request headers to match. Multiple header values are ANDed together, + meaning, a request MUST match all the specified headers. + At least one of headers or sourceCIDR condition must be specified. + items: + description: HeaderMatch defines the match attributes + within the HTTP Headers of the request. + properties: + invert: + default: false + description: |- + Invert specifies whether the value match result will be inverted. + Do not set this field when Type="Distinct", implying matching on any/all unique + values within the header. + type: boolean + name: + description: |- + Name of the HTTP header. + The header name is case-insensitive unless PreserveHeaderCase is set to true. + For example, "Foo" and "foo" are considered the same header. + maxLength: 256 + minLength: 1 + type: string + type: + default: Exact + description: Type specifies how to match + against the value of the header. + enum: + - Exact + - RegularExpression + - Distinct + type: string + value: + description: |- + Value within the HTTP header. + Do not set this field when Type="Distinct", implying matching on any/all unique + values within the header. + maxLength: 1024 + type: string + required: + - name + type: object + maxItems: 16 + type: array + sourceCIDR: + description: |- + SourceCIDR is the client IP Address range to match on. + At least one of headers or sourceCIDR condition must be specified. + properties: + type: + default: Exact + enum: + - Exact + - Distinct + type: string + value: + description: |- + Value 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`. + maxLength: 256 + minLength: 1 + type: string + required: + - value + type: object + type: object + maxItems: 8 + type: array + cost: + description: |- + Cost specifies the cost of requests and responses for the rule. + + This is optional and if not specified, the default behavior is to reduce the rate limit counters by 1 on + the request path and do not reduce the rate limit counters on the response path. + properties: + request: + description: |- + Request specifies the number to reduce the rate limit counters + on the request path. If this is not specified, the default behavior + is to reduce the rate limit counters by 1. + + When Envoy receives a request that matches the rule, it tries to reduce the + rate limit counters by the specified number. If the counter doesn't have + enough capacity, the request is rate limited. + properties: + from: + description: From specifies where to get the + rate limit cost. Currently, only "Number" + and "Metadata" are supported. + enum: + - Number + - Metadata + type: string + metadata: + description: Metadata specifies the per-request + metadata to retrieve the usage number from. + properties: + key: + description: Key is the key to retrieve + the usage number from the filter metadata. + type: string + namespace: + description: Namespace is the namespace + of the dynamic metadata. + type: string + required: + - key + - namespace + type: object + number: + description: |- + Number specifies the fixed usage number to reduce the rate limit counters. + Using zero can be used to only check the rate limit counters without reducing them. + format: int64 + type: integer + required: + - from + type: object + x-kubernetes-validations: + - message: only one of number or metadata can be + specified + rule: '!(has(self.number) && has(self.metadata))' + response: + description: |- + Response specifies the number to reduce the rate limit counters + after the response is sent back to the client or the request stream is closed. + + The cost is used to reduce the rate limit counters for the matching requests. + Since the reduction happens after the request stream is complete, the rate limit + won't be enforced for the current request, but for the subsequent matching requests. + + This is optional and if not specified, the rate limit counters are not reduced + on the response path. + + Currently, this is only supported for HTTP Global Rate Limits. + properties: + from: + description: From specifies where to get the + rate limit cost. Currently, only "Number" + and "Metadata" are supported. + enum: + - Number + - Metadata + type: string + metadata: + description: Metadata specifies the per-request + metadata to retrieve the usage number from. + properties: + key: + description: Key is the key to retrieve + the usage number from the filter metadata. + type: string + namespace: + description: Namespace is the namespace + of the dynamic metadata. + type: string + required: + - key + - namespace + type: object + number: + description: |- + Number specifies the fixed usage number to reduce the rate limit counters. + Using zero can be used to only check the rate limit counters without reducing them. + format: int64 + type: integer + required: + - from + type: object + x-kubernetes-validations: + - message: only one of number or metadata can be + specified + rule: '!(has(self.number) && has(self.metadata))' + type: object + limit: + description: |- + 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. + properties: + requests: + type: integer + unit: + description: |- + RateLimitUnit specifies the intervals for setting rate limits. + Valid RateLimitUnit values are "Second", "Minute", "Hour", and "Day". + enum: + - Second + - Minute + - Hour + - Day + type: string + required: + - requests + - unit + type: object + required: + - limit + type: object + maxItems: 16 + type: array + x-kubernetes-validations: + - message: response cost is not supported for Local Rate Limits + rule: self.all(foo, !has(foo.cost) || !has(foo.cost.response)) + type: object + type: + description: |- + Type decides the scope for the RateLimits. + Valid RateLimitType values are "Global" or "Local". + enum: + - Global + - Local + type: string + required: + - type + type: object + requestBuffer: + description: |- + RequestBuffer allows the gateway to buffer and fully receive each request from a client before continuing to send the request + upstream to the backends. This can be helpful to shield your backend servers from slow clients, and also to enforce a maximum size per request + as any requests larger than the buffer size will be rejected. + + This can have a negative performance impact so should only be enabled when necessary. + + When enabling this option, you should also configure your connection buffer size to account for these request buffers. There will also be an + increase in memory usage for Envoy that should be accounted for in your deployment settings. + properties: + limit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + Limit specifies the maximum allowed size in bytes for each incoming request buffer. + If exceeded, the request will be rejected with HTTP 413 Content Too Large. + + Accepts values in resource.Quantity format (e.g., "10Mi", "500Ki"). + x-kubernetes-int-or-string: true + type: object + responseOverride: + description: |- + ResponseOverride defines the configuration to override specific responses with a custom one. + If multiple configurations are specified, the first one to match wins. + items: + description: ResponseOverride defines the configuration to override + specific responses with a custom one. + properties: + match: + description: Match configuration. + properties: + statusCodes: + description: Status code to match on. The match evaluates + to true if any of the matches are successful. + items: + description: StatusCodeMatch defines the configuration + for matching a status code. + properties: + range: + description: Range contains the range of status codes. + properties: + end: + description: End of the range, including the end + value. + type: integer + start: + description: Start of the range, including the + start value. + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: end must be greater than start + rule: self.end > self.start + type: + allOf: + - enum: + - Value + - Range + - enum: + - Value + - Range + default: Value + description: |- + Type is the type of value. + Valid values are Value and Range, default is Value. + type: string + value: + description: Value contains the value of the status + code. + type: integer + required: + - type + type: object + x-kubernetes-validations: + - message: value must be set for type Value + rule: '(!has(self.type) || self.type == ''Value'')? + has(self.value) : true' + - message: range must be set for type Range + rule: '(has(self.type) && self.type == ''Range'')? has(self.range) + : true' + maxItems: 50 + minItems: 1 + type: array + required: + - statusCodes + type: object + response: + description: Response configuration. + properties: + body: + description: Body of the Custom Response + properties: + inline: + description: Inline contains the value as an inline + string. + type: string + type: + allOf: + - enum: + - Inline + - ValueRef + - enum: + - Inline + - ValueRef + default: Inline + description: |- + Type is the type of method to use to read the body value. + Valid values are Inline and ValueRef, default is Inline. + type: string + valueRef: + description: |- + ValueRef contains the contents of the body + specified as a local object reference. + Only a reference to ConfigMap is supported. + + The value of key `response.body` in the ConfigMap will be used as the response body. + If the key is not found, the first value in the ConfigMap will be used. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: inline must be set for type Inline + rule: '(!has(self.type) || self.type == ''Inline'')? has(self.inline) + : true' + - message: valueRef must be set for type ValueRef + rule: '(has(self.type) && self.type == ''ValueRef'')? + has(self.valueRef) : true' + - message: only ConfigMap is supported for ValueRef + rule: 'has(self.valueRef) ? self.valueRef.kind == ''ConfigMap'' + : true' + contentType: + description: Content Type of the response. This will be + set in the Content-Type header. + type: string + statusCode: + description: |- + Status Code of the Custom Response + If unset, does not override the status of response. + type: integer + type: object + required: + - match + - response + type: object + type: array + retry: + description: |- + Retry provides more advanced usage, allowing users to customize the number of retries, retry fallback strategy, and retry triggering conditions. + If not set, retry will be disabled. + properties: + numRetries: + default: 2 + description: NumRetries is the number of retries to be attempted. + Defaults to 2. + format: int32 + minimum: 0 + type: integer + perRetry: + description: PerRetry is the retry policy to be applied per retry + attempt. + properties: + backOff: + description: |- + Backoff is the backoff policy to be applied per retry attempt. gateway uses a fully jittered exponential + back-off algorithm for retries. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#config-http-filters-router-x-envoy-max-retries + properties: + baseInterval: + description: BaseInterval is the base interval between + retries. + format: duration + type: string + maxInterval: + description: |- + MaxInterval is the maximum interval between retries. This parameter is optional, but must be greater than or equal to the base_interval if set. + The default is 10 times the base_interval + format: duration + type: string + type: object + timeout: + description: Timeout is the timeout per retry attempt. + format: duration + type: string + type: object + retryOn: + description: |- + RetryOn specifies the retry trigger condition. + + If not specified, the default is to retry on connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes(503). + properties: + httpStatusCodes: + description: |- + HttpStatusCodes specifies the http status codes to be retried. + The retriable-status-codes trigger must also be configured for these status codes to trigger a retry. + items: + description: HTTPStatus defines the http status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + triggers: + description: Triggers specifies the retry trigger condition(Http/Grpc). + items: + description: TriggerEnum specifies the conditions that trigger + retries. + enum: + - 5xx + - gateway-error + - reset + - connect-failure + - retriable-4xx + - refused-stream + - retriable-status-codes + - cancelled + - deadline-exceeded + - internal + - resource-exhausted + - unavailable + type: string + type: array + type: object + type: object + targetRef: + description: |- + TargetRef is the name of the resource this policy is being attached to. + This policy and the TargetRef MUST be in the same namespace for this + Policy to have effect + + Deprecated: use targetRefs/targetSelectors instead + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + targetRefs: + description: |- + TargetRefs are the names of the Gateway resources this policy + is being attached to. + items: + description: |- + LocalPolicyTargetReferenceWithSectionName identifies an API object to apply a + direct policy to. This should be used as part of Policy resources that can + target single resources. For more information on how this policy attachment + mode works, and a sample Policy resource, refer to the policy attachment + documentation for Gateway API. + + Note: This should only be used for direct policy attachment when references + to SectionName are actually needed. In all other cases, + LocalPolicyTargetReference should be used. + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + type: array + targetSelectors: + description: TargetSelectors allow targeting resources for this policy + based on labels + items: + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group that this selector targets. + Defaults to gateway.networking.k8s.io + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the resource kind that this selector targets. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: MatchLabels are the set of label selectors for + identifying the targeted resource + type: object + required: + - kind + type: object + x-kubernetes-validations: + - message: group must be gateway.networking.k8s.io + rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' + : true ' + type: array + tcpKeepalive: + description: |- + TcpKeepalive settings associated with the upstream client connection. + Disabled by default. + properties: + idleTime: + description: |- + The duration a connection needs to be idle before keep-alive + probes start being sent. + The duration format is + Defaults to `7200s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + The duration between keep-alive probes. + Defaults to `75s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + probes: + description: |- + The total number of unacknowledged probes to send before deciding + the connection is dead. + Defaults to 9. + format: int32 + type: integer + type: object + telemetry: + description: |- + Telemetry configures the telemetry settings for the policy target (Gateway or xRoute). + This will override the telemetry settings in the EnvoyProxy resource. + properties: + tracing: + description: Tracing configures the tracing settings for the backend + or HTTPRoute. + properties: + customTags: + additionalProperties: + properties: + environment: + description: |- + Environment adds value from environment variable to each span. + It's required when the type is "Environment". + properties: + defaultValue: + description: DefaultValue defines the default value + to use if the environment variable is not set. + type: string + name: + description: Name defines the name of the environment + variable which to extract the value from. + type: string + required: + - name + type: object + literal: + description: |- + Literal adds hard-coded value to each span. + It's required when the type is "Literal". + properties: + value: + description: Value defines the hard-coded value + to add to each span. + type: string + required: + - value + type: object + requestHeader: + description: |- + RequestHeader adds value from request header to each span. + It's required when the type is "RequestHeader". + properties: + defaultValue: + description: DefaultValue defines the default value + to use if the request header is not set. + type: string + name: + description: Name defines the name of the request + header which to extract the value from. + type: string + required: + - name + type: object + type: + default: Literal + description: Type defines the type of custom tag. + enum: + - Literal + - Environment + - RequestHeader + type: string + required: + - type + type: object + description: |- + CustomTags defines the custom tags to add to each span. + If provider is kubernetes, pod name and namespace are added by default. + type: object + samplingFraction: + description: |- + SamplingFraction represents the fraction of requests that should be + selected for tracing if no prior sampling decision has been made. + + This will take precedence over sampling fraction on EnvoyProxy if set. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal to denominator + rule: self.numerator <= self.denominator + type: object + type: object + timeout: + description: Timeout settings for the backend connections. + properties: + http: + description: Timeout settings for HTTP. + properties: + connectionIdleTimeout: + description: |- + The idle timeout for an HTTP connection. Idle time is defined as a period in which there are no active requests in the connection. + Default: 1 hour. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + maxConnectionDuration: + description: |- + The maximum duration of an HTTP connection. + Default: unlimited. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + requestTimeout: + description: RequestTimeout is the time until which entire + response is received from the upstream. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + tcp: + description: Timeout settings for TCP. + properties: + connectTimeout: + description: |- + The timeout for network connection establishment, including TCP and TLS handshakes. + Default: 10 seconds. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + type: object + useClientProtocol: + description: |- + UseClientProtocol configures Envoy to prefer sending requests to backends using + the same HTTP protocol that the incoming request used. Defaults to false, which means + that Envoy will use the protocol indicated by the attached BackendRef. + type: boolean + type: object + x-kubernetes-validations: + - message: either targetRef or targetRefs must be used + rule: '(has(self.targetRef) && !has(self.targetRefs)) || (!has(self.targetRef) + && has(self.targetRefs)) || (has(self.targetSelectors) && self.targetSelectors.size() + > 0) ' + - message: this policy can only have a targetRef.group of gateway.networking.k8s.io + rule: 'has(self.targetRef) ? self.targetRef.group == ''gateway.networking.k8s.io'' + : true ' + - message: this policy can only have a targetRef.kind of Gateway/HTTPRoute/GRPCRoute/TCPRoute/UDPRoute/TLSRoute + rule: 'has(self.targetRef) ? self.targetRef.kind in [''Gateway'', ''HTTPRoute'', + ''GRPCRoute'', ''UDPRoute'', ''TCPRoute'', ''TLSRoute''] : true' + - message: this policy does not yet support the sectionName field + rule: 'has(self.targetRef) ? !has(self.targetRef.sectionName) : true' + - message: this policy can only have a targetRefs[*].group of gateway.networking.k8s.io + rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, ref.group == + ''gateway.networking.k8s.io'') : true ' + - message: this policy can only have a targetRefs[*].kind of Gateway/HTTPRoute/GRPCRoute/TCPRoute/UDPRoute/TLSRoute + rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, ref.kind in [''Gateway'', + ''HTTPRoute'', ''GRPCRoute'', ''UDPRoute'', ''TCPRoute'', ''TLSRoute'']) + : true ' + - message: this policy does not yet support the sectionName field + rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, !has(ref.sectionName)) + : true' + status: + description: status defines the current status of BackendTrafficPolicy. + properties: + ancestors: + description: |- + Ancestors is a list of ancestor resources (usually Gateways) that are + associated with the policy, and the status of the policy with respect to + each ancestor. When this policy attaches to a parent, the controller that + manages the parent and the ancestors MUST add an entry to this list when + the controller first sees the policy and SHOULD update the entry as + appropriate when the relevant ancestor is modified. + + Note that choosing the relevant ancestor is left to the Policy designers; + an important part of Policy design is designing the right object level at + which to namespace this status. + + Note also that implementations MUST ONLY populate ancestor status for + the Ancestor resources they are responsible for. Implementations MUST + use the ControllerName field to uniquely identify the entries in this list + that they are responsible for. + + Note that to achieve this, the list of PolicyAncestorStatus structs + MUST be treated as a map with a composite key, made up of the AncestorRef + and ControllerName fields combined. + + A maximum of 16 ancestors will be represented in this list. An empty list + means the Policy is not relevant for any ancestors. + + If this slice is full, implementations MUST NOT add further entries. + Instead they MUST consider the policy unimplementable and signal that + on any related resources such as the ancestor that would be referenced + here. For example, if this list was full on BackendTLSPolicy, no + additional Gateways would be able to reference the Service targeted by + the BackendTLSPolicy. + items: + description: |- + PolicyAncestorStatus describes the status of a route with respect to an + associated Ancestor. + + Ancestors refer to objects that are either the Target of a policy or above it + in terms of object hierarchy. For example, if a policy targets a Service, the + Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and + the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most + useful object to place Policy status on, so we recommend that implementations + SHOULD use Gateway as the PolicyAncestorStatus object unless the designers + have a _very_ good reason otherwise. + + In the context of policy attachment, the Ancestor is used to distinguish which + resource results in a distinct application of this policy. For example, if a policy + targets a Service, it may have a distinct result per attached Gateway. + + Policies targeting the same resource may have different effects depending on the + ancestors of those resources. For example, different Gateways targeting the same + Service may have different capabilities, especially if they have different underlying + implementations. + + For example, in BackendTLSPolicy, the Policy attaches to a Service that is + used as a backend in a HTTPRoute that is itself attached to a Gateway. + In this case, the relevant object for status is the Gateway, and that is the + ancestor object referred to in this status. + + Note that a parent is also an ancestor, so for objects where the parent is the + relevant object for status, this struct SHOULD still be used. + + This struct is intended to be used in a slice that's effectively a map, + with a composite key made up of the AncestorRef and the ControllerName. + properties: + ancestorRef: + description: |- + AncestorRef corresponds with a ParentRef in the spec that this + PolicyAncestorStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: Conditions describes the status of the Policy with + respect to the given Ancestor. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - controllerName + type: object + maxItems: 16 + type: array + required: + - ancestors + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: gateway-crds-helm/templates/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 + name: clienttrafficpolicies.gateway.envoyproxy.io +spec: + group: gateway.envoyproxy.io + names: + categories: + - envoy-gateway + kind: ClientTrafficPolicy + listKind: ClientTrafficPolicyList + plural: clienttrafficpolicies + shortNames: + - ctp + singular: clienttrafficpolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + ClientTrafficPolicy allows the user to configure the behavior of the connection + between the downstream client and Envoy Proxy listener. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of ClientTrafficPolicy. + properties: + clientIPDetection: + description: ClientIPDetectionSettings provides configuration for + determining the original client IP address for requests. + properties: + customHeader: + description: |- + CustomHeader provides configuration for determining the client IP address for a request based on + a trusted custom HTTP header. This uses the custom_header original IP detection extension. + Refer to https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/http/original_ip_detection/custom_header/v3/custom_header.proto + for more details. + properties: + failClosed: + description: |- + FailClosed is a switch used to control the flow of traffic when client IP detection + fails. If set to true, the listener will respond with 403 Forbidden when the client + IP address cannot be determined. + type: boolean + name: + description: Name of the header containing the original downstream + remote address, if present. + maxLength: 255 + minLength: 1 + pattern: ^[A-Za-z0-9-]+$ + type: string + required: + - name + type: object + xForwardedFor: + description: XForwardedForSettings provides configuration for + using X-Forwarded-For headers for determining the client IP + address. + properties: + numTrustedHops: + description: |- + NumTrustedHops controls the number of additional ingress proxy hops from the right side of XFF HTTP + headers to trust when determining the origin client's IP address. + Only one of NumTrustedHops and TrustedCIDRs must be set. + format: int32 + type: integer + trustedCIDRs: + description: |- + TrustedCIDRs is a list of CIDR ranges to trust when evaluating + the remote IP address to determine the original client’s IP address. + When the remote IP address matches a trusted CIDR and the x-forwarded-for header was sent, + each entry in the x-forwarded-for header is evaluated from right to left + and the first public non-trusted address is used as the original client address. + If all addresses in x-forwarded-for are within the trusted list, the first (leftmost) entry is used. + Only one of NumTrustedHops and TrustedCIDRs must be set. + items: + description: |- + CIDR defines a CIDR Address range. + A CIDR can be an IPv4 address range such as "192.168.1.0/24" or an IPv6 address range such as "2001:0db8:11a3:09d7::/64". + pattern: ((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/([0-9]+))|((([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/([0-9]+)) + type: string + minItems: 1 + type: array + type: object + x-kubernetes-validations: + - message: only one of numTrustedHops or trustedCIDRs must be + set + rule: (has(self.numTrustedHops) && !has(self.trustedCIDRs)) + || (!has(self.numTrustedHops) && has(self.trustedCIDRs)) + type: object + x-kubernetes-validations: + - message: customHeader cannot be used in conjunction with xForwardedFor + rule: '!(has(self.xForwardedFor) && has(self.customHeader))' + connection: + description: Connection includes client connection settings. + properties: + bufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + BufferLimit provides configuration for the maximum buffer size in bytes for each incoming connection. + BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space. + For example, 20Mi, 1Gi, 256Ki etc. + Note that when the suffix is not provided, the value is interpreted as bytes. + Default: 32768 bytes. + x-kubernetes-int-or-string: true + connectionLimit: + description: ConnectionLimit defines limits related to connections + properties: + closeDelay: + description: |- + CloseDelay defines the delay to use before closing connections that are rejected + once the limit value is reached. + Default: none. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + value: + description: |- + Value of the maximum concurrent connections limit. + When the limit is reached, incoming connections will be closed after the CloseDelay duration. + format: int64 + minimum: 1 + type: integer + required: + - value + type: object + socketBufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + SocketBufferLimit provides configuration for the maximum buffer size in bytes for each incoming socket. + SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space. + For example, 20Mi, 1Gi, 256Ki etc. + Note that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + type: object + enableProxyProtocol: + description: |- + EnableProxyProtocol interprets the ProxyProtocol header and adds the + Client Address into the X-Forwarded-For header. + Note Proxy Protocol must be present when this field is set, else the connection + is closed. + type: boolean + headers: + description: HeaderSettings provides configuration for header management. + properties: + disableRateLimitHeaders: + description: |- + DisableRateLimitHeaders configures Envoy Proxy to omit the "X-RateLimit-" response headers + when rate limiting is enabled. + type: boolean + earlyRequestHeaders: + description: |- + EarlyRequestHeaders defines settings for early request header modification, before envoy performs + routing, tracing and built-in header manipulation. + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header name and + value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be + matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header name and + value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be + matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + enableEnvoyHeaders: + description: |- + EnableEnvoyHeaders configures Envoy Proxy to add the "X-Envoy-" headers to requests + and responses. + type: boolean + preserveXRequestID: + description: |- + PreserveXRequestID configures Envoy to keep the X-Request-ID header if passed for a request that is edge + (Edge request is the request from external clients to front Envoy) and not reset it, which is the current Envoy behaviour. + Defaults to false and cannot be combined with RequestID. + Deprecated: use RequestID=Preserve instead + type: boolean + requestID: + description: |- + RequestID configures Envoy's behavior for handling the `X-Request-ID` header. + Defaults to `Generate` and builds the `X-Request-ID` for every request and ignores pre-existing values from the edge. + (An "edge request" refers to a request from an external client to the Envoy entrypoint.) + enum: + - PreserveOrGenerate + - Preserve + - Generate + - Disable + type: string + withUnderscoresAction: + description: |- + WithUnderscoresAction configures the action to take when an HTTP header with underscores + is encountered. The default action is to reject the request. + enum: + - Allow + - RejectRequest + - DropHeader + type: string + xForwardedClientCert: + description: |- + XForwardedClientCert configures how Envoy Proxy handle the x-forwarded-client-cert (XFCC) HTTP header. + + x-forwarded-client-cert (XFCC) is an HTTP header used to forward the certificate + information of part or all of the clients or proxies that a request has flowed through, + on its way from the client to the server. + + Envoy proxy may choose to sanitize/append/forward the XFCC header before proxying the request. + + If not set, the default behavior is sanitizing the XFCC header. + properties: + certDetailsToAdd: + description: |- + CertDetailsToAdd specifies the fields in the client certificate to be forwarded in the XFCC header. + + Hash(the SHA 256 digest of the current client certificate) and By(the Subject Alternative Name) + are always included if the client certificate is forwarded. + + This field is only applicable when the mode is set to `AppendForward` or + `SanitizeSet` and the client connection is mTLS. + items: + description: XFCCCertData specifies the fields in the client + certificate to be forwarded in the XFCC header. + enum: + - Subject + - Cert + - Chain + - DNS + - URI + type: string + maxItems: 5 + type: array + mode: + description: |- + Mode defines how XFCC header is handled by Envoy Proxy. + If not set, the default mode is `Sanitize`. + enum: + - Sanitize + - ForwardOnly + - AppendForward + - SanitizeSet + - AlwaysForwardOnly + type: string + type: object + x-kubernetes-validations: + - message: certDetailsToAdd can only be set when mode is AppendForward + or SanitizeSet + rule: '(has(self.certDetailsToAdd) && self.certDetailsToAdd.size() + > 0) ? (self.mode == ''AppendForward'' || self.mode == ''SanitizeSet'') + : true' + type: object + x-kubernetes-validations: + - message: preserveXRequestID and requestID cannot both be set. + rule: '!(has(self.preserveXRequestID) && has(self.requestID))' + healthCheck: + description: HealthCheck provides configuration for determining whether + the HTTP/HTTPS listener is healthy. + properties: + path: + description: Path specifies the HTTP path to match on for health + check requests. + maxLength: 1024 + minLength: 1 + type: string + required: + - path + type: object + http1: + description: HTTP1 provides HTTP/1 configuration on the listener. + properties: + enableTrailers: + description: EnableTrailers defines if HTTP/1 trailers should + be proxied by Envoy. + type: boolean + http10: + description: HTTP10 turns on support for HTTP/1.0 and HTTP/0.9 + requests. + properties: + useDefaultHost: + description: |- + UseDefaultHost defines if the HTTP/1.0 request is missing the Host header, + then the hostname associated with the listener should be injected into the + request. + If this is not set and an HTTP/1.0 request arrives without a host, then + it will be rejected. + type: boolean + type: object + preserveHeaderCase: + description: |- + PreserveHeaderCase defines if Envoy should preserve the letter case of headers. + By default, Envoy will lowercase all the headers. + type: boolean + type: object + http2: + description: HTTP2 provides HTTP/2 configuration on the listener. + properties: + initialConnectionWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialConnectionWindowSize sets the initial window size for HTTP/2 connections. + If not set, the default value is 1 MiB. + x-kubernetes-int-or-string: true + initialStreamWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialStreamWindowSize sets the initial window size for HTTP/2 streams. + If not set, the default value is 64 KiB(64*1024). + x-kubernetes-int-or-string: true + maxConcurrentStreams: + description: |- + MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection. + If not set, the default value is 100. + format: int32 + maximum: 2147483647 + minimum: 1 + type: integer + onInvalidMessage: + description: |- + OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error + It's recommended for L2 Envoy deployments to set this value to TerminateStream. + https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two + Default: TerminateConnection + type: string + type: object + http3: + description: HTTP3 provides HTTP/3 configuration on the listener. + type: object + path: + description: Path enables managing how the incoming path set by clients + can be normalized. + properties: + disableMergeSlashes: + description: |- + DisableMergeSlashes allows disabling the default configuration of merging adjacent + slashes in the path. + Note that slash merging is not part of the HTTP spec and is provided for convenience. + type: boolean + escapedSlashesAction: + description: |- + EscapedSlashesAction determines how %2f, %2F, %5c, or %5C sequences in the path URI + should be handled. + The default is UnescapeAndRedirect. + enum: + - KeepUnchanged + - RejectRequest + - UnescapeAndForward + - UnescapeAndRedirect + type: string + type: object + targetRef: + description: |- + TargetRef is the name of the resource this policy is being attached to. + This policy and the TargetRef MUST be in the same namespace for this + Policy to have effect + + Deprecated: use targetRefs/targetSelectors instead + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + targetRefs: + description: |- + TargetRefs are the names of the Gateway resources this policy + is being attached to. + items: + description: |- + LocalPolicyTargetReferenceWithSectionName identifies an API object to apply a + direct policy to. This should be used as part of Policy resources that can + target single resources. For more information on how this policy attachment + mode works, and a sample Policy resource, refer to the policy attachment + documentation for Gateway API. + + Note: This should only be used for direct policy attachment when references + to SectionName are actually needed. In all other cases, + LocalPolicyTargetReference should be used. + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + type: array + targetSelectors: + description: TargetSelectors allow targeting resources for this policy + based on labels + items: + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group that this selector targets. + Defaults to gateway.networking.k8s.io + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the resource kind that this selector targets. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: MatchLabels are the set of label selectors for + identifying the targeted resource + type: object + required: + - kind + type: object + x-kubernetes-validations: + - message: group must be gateway.networking.k8s.io + rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' + : true ' + type: array + tcpKeepalive: + description: |- + TcpKeepalive settings associated with the downstream client connection. + If defined, sets SO_KEEPALIVE on the listener socket to enable TCP Keepalives. + Disabled by default. + properties: + idleTime: + description: |- + The duration a connection needs to be idle before keep-alive + probes start being sent. + The duration format is + Defaults to `7200s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + The duration between keep-alive probes. + Defaults to `75s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + probes: + description: |- + The total number of unacknowledged probes to send before deciding + the connection is dead. + Defaults to 9. + format: int32 + type: integer + type: object + timeout: + description: Timeout settings for the client connections. + properties: + http: + description: Timeout settings for HTTP. + properties: + idleTimeout: + description: |- + IdleTimeout for an HTTP connection. Idle time is defined as a period in which there are no active requests in the connection. + Default: 1 hour. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + requestReceivedTimeout: + description: |- + RequestReceivedTimeout is the duration envoy waits for the complete request reception. This timer starts upon request + initiation and stops when either the last byte of the request is sent upstream or when the response begins. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + tcp: + description: Timeout settings for TCP. + properties: + idleTimeout: + description: |- + IdleTimeout for a TCP connection. Idle time is defined as a period in which there are no + bytes sent or received on either the upstream or downstream connection. + Default: 1 hour. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + type: object + tls: + description: TLS settings configure TLS termination settings with + the downstream client. + properties: + alpnProtocols: + description: |- + ALPNProtocols supplies the list of ALPN protocols that should be + exposed by the listener or used by the proxy to connect to the backend. + Defaults: + 1. HTTPS Routes: h2 and http/1.1 are enabled in listener context. + 2. Other Routes: ALPN is disabled. + 3. Backends: proxy uses the appropriate ALPN options for the backend protocol. + When an empty list is provided, the ALPN TLS extension is disabled. + Supported values are: + - http/1.0 + - http/1.1 + - h2 + items: + description: ALPNProtocol specifies the protocol to be negotiated + using ALPN + enum: + - http/1.0 + - http/1.1 + - h2 + type: string + type: array + ciphers: + description: |- + Ciphers specifies the set of cipher suites supported when + negotiating TLS 1.0 - 1.2. This setting has no effect for TLS 1.3. + In non-FIPS Envoy Proxy builds the default cipher list is: + - [ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305] + - [ECDHE-RSA-AES128-GCM-SHA256|ECDHE-RSA-CHACHA20-POLY1305] + - ECDHE-ECDSA-AES256-GCM-SHA384 + - ECDHE-RSA-AES256-GCM-SHA384 + In builds using BoringSSL FIPS the default cipher list is: + - ECDHE-ECDSA-AES128-GCM-SHA256 + - ECDHE-RSA-AES128-GCM-SHA256 + - ECDHE-ECDSA-AES256-GCM-SHA384 + - ECDHE-RSA-AES256-GCM-SHA384 + items: + type: string + type: array + clientValidation: + description: |- + ClientValidation specifies the configuration to validate the client + initiating the TLS connection to the Gateway listener. + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to + Kubernetes objects that contain TLS certificates of + the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the client. + + A single reference to a Kubernetes ConfigMap or a Kubernetes Secret, + with the CA certificate in a key named `ca.crt` is currently supported. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. + items: + description: |- + SecretObjectReference identifies an API object including its namespace, + defaulting to Secret. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example + "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 8 + type: array + optional: + description: |- + Optional set to true accepts connections even when a client doesn't present a certificate. + Defaults to false, which rejects connections without a valid client certificate. + type: boolean + type: object + ecdhCurves: + description: |- + ECDHCurves specifies the set of supported ECDH curves. + In non-FIPS Envoy Proxy builds the default curves are: + - X25519 + - P-256 + In builds using BoringSSL FIPS the default curve is: + - P-256 + items: + type: string + type: array + maxVersion: + description: |- + Max specifies the maximal TLS protocol version to allow + The default is TLS 1.3 if this is not specified. + enum: + - Auto + - "1.0" + - "1.1" + - "1.2" + - "1.3" + type: string + minVersion: + description: |- + Min specifies the minimal TLS protocol version to allow. + The default is TLS 1.2 if this is not specified. + enum: + - Auto + - "1.0" + - "1.1" + - "1.2" + - "1.3" + type: string + session: + description: Session defines settings related to TLS session management. + properties: + resumption: + description: |- + Resumption determines the proxy's supported TLS session resumption option. + By default, Envoy Gateway does not enable session resumption. Use sessionResumption to + enable stateful and stateless session resumption. Users should consider security impacts + of different resumption methods. Performance gains from resumption are diminished when + Envoy proxy is deployed with more than one replica. + properties: + stateful: + description: Stateful defines setting for stateful (session-id + based) session resumption + type: object + stateless: + description: Stateless defines setting for stateless (session-ticket + based) session resumption + type: object + type: object + type: object + signatureAlgorithms: + description: |- + SignatureAlgorithms specifies which signature algorithms the listener should + support. + items: + type: string + type: array + type: object + x-kubernetes-validations: + - message: setting ciphers has no effect if the minimum possible TLS + version is 1.3 + rule: 'has(self.minVersion) && self.minVersion == ''1.3'' ? !has(self.ciphers) + : true' + - message: minVersion must be smaller or equal to maxVersion + rule: 'has(self.minVersion) && has(self.maxVersion) ? {"Auto":0,"1.0":1,"1.1":2,"1.2":3,"1.3":4}[self.minVersion] + <= {"1.0":1,"1.1":2,"1.2":3,"1.3":4,"Auto":5}[self.maxVersion] + : !has(self.minVersion) && has(self.maxVersion) ? 3 <= {"1.0":1,"1.1":2,"1.2":3,"1.3":4,"Auto":5}[self.maxVersion] + : true' + type: object + x-kubernetes-validations: + - message: either targetRef or targetRefs must be used + rule: '(has(self.targetRef) && !has(self.targetRefs)) || (!has(self.targetRef) + && has(self.targetRefs)) || (has(self.targetSelectors) && self.targetSelectors.size() + > 0) ' + - message: this policy can only have a targetRef.group of gateway.networking.k8s.io + rule: 'has(self.targetRef) ? self.targetRef.group == ''gateway.networking.k8s.io'' + : true' + - message: this policy can only have a targetRef.kind of Gateway + rule: 'has(self.targetRef) ? self.targetRef.kind == ''Gateway'' : true' + - message: this policy can only have a targetRefs[*].group of gateway.networking.k8s.io + rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, ref.group == + ''gateway.networking.k8s.io'') : true' + - message: this policy can only have a targetRefs[*].kind of Gateway + rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, ref.kind == ''Gateway'') + : true' + status: + description: Status defines the current status of ClientTrafficPolicy. + properties: + ancestors: + description: |- + Ancestors is a list of ancestor resources (usually Gateways) that are + associated with the policy, and the status of the policy with respect to + each ancestor. When this policy attaches to a parent, the controller that + manages the parent and the ancestors MUST add an entry to this list when + the controller first sees the policy and SHOULD update the entry as + appropriate when the relevant ancestor is modified. + + Note that choosing the relevant ancestor is left to the Policy designers; + an important part of Policy design is designing the right object level at + which to namespace this status. + + Note also that implementations MUST ONLY populate ancestor status for + the Ancestor resources they are responsible for. Implementations MUST + use the ControllerName field to uniquely identify the entries in this list + that they are responsible for. + + Note that to achieve this, the list of PolicyAncestorStatus structs + MUST be treated as a map with a composite key, made up of the AncestorRef + and ControllerName fields combined. + + A maximum of 16 ancestors will be represented in this list. An empty list + means the Policy is not relevant for any ancestors. + + If this slice is full, implementations MUST NOT add further entries. + Instead they MUST consider the policy unimplementable and signal that + on any related resources such as the ancestor that would be referenced + here. For example, if this list was full on BackendTLSPolicy, no + additional Gateways would be able to reference the Service targeted by + the BackendTLSPolicy. + items: + description: |- + PolicyAncestorStatus describes the status of a route with respect to an + associated Ancestor. + + Ancestors refer to objects that are either the Target of a policy or above it + in terms of object hierarchy. For example, if a policy targets a Service, the + Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and + the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most + useful object to place Policy status on, so we recommend that implementations + SHOULD use Gateway as the PolicyAncestorStatus object unless the designers + have a _very_ good reason otherwise. + + In the context of policy attachment, the Ancestor is used to distinguish which + resource results in a distinct application of this policy. For example, if a policy + targets a Service, it may have a distinct result per attached Gateway. + + Policies targeting the same resource may have different effects depending on the + ancestors of those resources. For example, different Gateways targeting the same + Service may have different capabilities, especially if they have different underlying + implementations. + + For example, in BackendTLSPolicy, the Policy attaches to a Service that is + used as a backend in a HTTPRoute that is itself attached to a Gateway. + In this case, the relevant object for status is the Gateway, and that is the + ancestor object referred to in this status. + + Note that a parent is also an ancestor, so for objects where the parent is the + relevant object for status, this struct SHOULD still be used. + + This struct is intended to be used in a slice that's effectively a map, + with a composite key made up of the AncestorRef and the ControllerName. + properties: + ancestorRef: + description: |- + AncestorRef corresponds with a ParentRef in the spec that this + PolicyAncestorStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: Conditions describes the status of the Policy with + respect to the given Ancestor. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - controllerName + type: object + maxItems: 16 + type: array + required: + - ancestors + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 + name: envoyextensionpolicies.gateway.envoyproxy.io +spec: + group: gateway.envoyproxy.io + names: + kind: EnvoyExtensionPolicy + listKind: EnvoyExtensionPolicyList + plural: envoyextensionpolicies + shortNames: + - eep + singular: envoyextensionpolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: EnvoyExtensionPolicy allows the user to configure various envoy + extensibility options for the Gateway. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of EnvoyExtensionPolicy. + properties: + extProc: + description: |- + ExtProc is an ordered list of external processing filters + that should be added to the envoy filter chain + items: + description: ExtProc defines the configuration for External Processing + filter. + properties: + backendRef: + description: |- + BackendRef references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + + Deprecated: Use BackendRefs instead. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + backendRefs: + description: |- + BackendRefs references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + items: + description: BackendRef defines how an ObjectReference that + is specific to BackendRef. + properties: + fallback: + description: |- + Fallback indicates whether the backend is designated as a fallback. + Multiple fallback backends can be configured. + It is highly recommended to configure active or passive health checks to ensure that failover can be detected + when the active backends become unhealthy and to automatically readjust once the primary backends are healthy again. + The overprovisioning factor is set to 1.4, meaning the fallback backends will only start receiving traffic when + the health of the active backends falls below 72%. + type: boolean + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + backendSettings: + description: |- + BackendSettings holds configuration for managing the connection + to the backend. + properties: + circuitBreaker: + description: |- + Circuit Breaker settings for the upstream connections and requests. + If not set, circuit breakers will be enabled with the default thresholds + properties: + maxConnections: + default: 1024 + description: The maximum number of connections that + Envoy will establish to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRequests: + default: 1024 + description: The maximum number of parallel requests + that Envoy will make to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRetries: + default: 1024 + description: The maximum number of parallel retries + that Envoy will make to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxPendingRequests: + default: 1024 + description: The maximum number of pending requests + that Envoy will queue to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxRequestsPerConnection: + description: |- + The maximum number of requests that Envoy will make over a single connection to the referenced backend defined within a xRoute rule. + Default: unlimited. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + perEndpoint: + description: PerEndpoint defines Circuit Breakers that + will apply per-endpoint for an upstream cluster + properties: + maxConnections: + default: 1024 + description: MaxConnections configures the maximum + number of connections that Envoy will establish + per-endpoint to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + type: object + type: object + connection: + description: Connection includes backend connection settings. + properties: + bufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + BufferLimit Soft limit on size of the cluster’s connections read and write buffers. + BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space. + If unspecified, an implementation defined default is applied (32768 bytes). + For example, 20Mi, 1Gi, 256Ki etc. + Note: that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + socketBufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + SocketBufferLimit provides configuration for the maximum buffer size in bytes for each socket + to backend. + SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space. + For example, 20Mi, 1Gi, 256Ki etc. + Note that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + type: object + dns: + description: DNS includes dns resolution settings. + properties: + dnsRefreshRate: + description: |- + DNSRefreshRate specifies the rate at which DNS records should be refreshed. + Defaults to 30 seconds. + type: string + lookupFamily: + description: |- + LookupFamily determines how Envoy would resolve DNS for Routes where the backend is specified as a fully qualified domain name (FQDN). + If set, this configuration overrides other defaults. + enum: + - IPv4 + - IPv6 + - IPv4Preferred + - IPv6Preferred + - IPv4AndIPv6 + type: string + respectDnsTtl: + description: |- + RespectDNSTTL indicates whether the DNS Time-To-Live (TTL) should be respected. + If the value is set to true, the DNS refresh rate will be set to the resource record’s TTL. + Defaults to true. + type: boolean + type: object + healthCheck: + description: HealthCheck allows gateway to perform active + health checking on backends. + properties: + active: + description: Active health check configuration + properties: + grpc: + description: |- + GRPC defines the configuration of the GRPC health checker. + It's optional, and can only be used if the specified type is GRPC. + properties: + service: + description: |- + Service to send in the health check request. + If this is not specified, then the health check request applies to the entire + server and not to a specific service. + type: string + type: object + healthyThreshold: + default: 1 + description: HealthyThreshold defines the number + of healthy health checks required before a backend + host is marked healthy. + format: int32 + minimum: 1 + type: integer + http: + description: |- + HTTP defines the configuration of http health checker. + It's required while the health checker type is HTTP. + properties: + expectedResponse: + description: ExpectedResponse defines a list + of HTTP expected responses to match. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of the + payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, binary + field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + expectedStatuses: + description: |- + ExpectedStatuses defines a list of HTTP response statuses considered healthy. + Defaults to 200 only + items: + description: HTTPStatus defines the http status + code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + method: + description: |- + Method defines the HTTP method used for health checking. + Defaults to GET + type: string + path: + description: Path defines the HTTP path that + will be requested during health checking. + maxLength: 1024 + minLength: 1 + type: string + required: + - path + type: object + interval: + default: 3s + description: Interval defines the time between active + health checks. + format: duration + type: string + tcp: + description: |- + TCP defines the configuration of tcp health checker. + It's required while the health checker type is TCP. + properties: + receive: + description: Receive defines the expected response + payload. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of the + payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, binary + field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + send: + description: Send defines the request payload. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of the + payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, binary + field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + type: object + timeout: + default: 1s + description: Timeout defines the time to wait for + a health check response. + format: duration + type: string + type: + allOf: + - enum: + - HTTP + - TCP + - GRPC + - enum: + - HTTP + - TCP + - GRPC + description: Type defines the type of health checker. + type: string + unhealthyThreshold: + default: 3 + description: UnhealthyThreshold defines the number + of unhealthy health checks required before a backend + host is marked unhealthy. + format: int32 + minimum: 1 + type: integer + required: + - type + type: object + x-kubernetes-validations: + - message: If Health Checker type is HTTP, http field + needs to be set. + rule: 'self.type == ''HTTP'' ? has(self.http) : !has(self.http)' + - message: If Health Checker type is TCP, tcp field + needs to be set. + rule: 'self.type == ''TCP'' ? has(self.tcp) : !has(self.tcp)' + - message: The grpc field can only be set if the Health + Checker type is GRPC. + rule: 'has(self.grpc) ? self.type == ''GRPC'' : true' + panicThreshold: + description: |- + When number of unhealthy endpoints for a backend reaches this threshold + Envoy will disregard health status and balance across all endpoints. + It's designed to prevent a situation in which host failures cascade throughout the cluster + as load increases. If not set, the default value is 50%. To disable panic mode, set value to `0`. + format: int32 + maximum: 100 + minimum: 0 + type: integer + passive: + description: Passive passive check configuration + properties: + baseEjectionTime: + default: 30s + description: BaseEjectionTime defines the base duration + for which a host will be ejected on consecutive + failures. + format: duration + type: string + consecutive5XxErrors: + default: 5 + description: Consecutive5xxErrors sets the number + of consecutive 5xx errors triggering ejection. + format: int32 + type: integer + consecutiveGatewayErrors: + default: 0 + description: ConsecutiveGatewayErrors sets the number + of consecutive gateway errors triggering ejection. + format: int32 + type: integer + consecutiveLocalOriginFailures: + default: 5 + description: |- + ConsecutiveLocalOriginFailures sets the number of consecutive local origin failures triggering ejection. + Parameter takes effect only when split_external_local_origin_errors is set to true. + format: int32 + type: integer + interval: + default: 3s + description: Interval defines the time between passive + health checks. + format: duration + type: string + maxEjectionPercent: + default: 10 + description: MaxEjectionPercent sets the maximum + percentage of hosts in a cluster that can be ejected. + format: int32 + type: integer + splitExternalLocalOriginErrors: + default: false + description: SplitExternalLocalOriginErrors enables + splitting of errors between external and local + origin. + type: boolean + type: object + type: object + http2: + description: HTTP2 provides HTTP/2 configuration for backend + connections. + properties: + initialConnectionWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialConnectionWindowSize sets the initial window size for HTTP/2 connections. + If not set, the default value is 1 MiB. + x-kubernetes-int-or-string: true + initialStreamWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialStreamWindowSize sets the initial window size for HTTP/2 streams. + If not set, the default value is 64 KiB(64*1024). + x-kubernetes-int-or-string: true + maxConcurrentStreams: + description: |- + MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection. + If not set, the default value is 100. + format: int32 + maximum: 2147483647 + minimum: 1 + type: integer + onInvalidMessage: + description: |- + OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error + It's recommended for L2 Envoy deployments to set this value to TerminateStream. + https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two + Default: TerminateConnection + type: string + type: object + loadBalancer: + description: |- + LoadBalancer policy to apply when routing traffic from the gateway to + the backend endpoints. Defaults to `LeastRequest`. + properties: + consistentHash: + description: |- + ConsistentHash defines the configuration when the load balancer type is + set to ConsistentHash + properties: + cookie: + description: Cookie configures the cookie hash policy + when the consistent hash type is set to Cookie. + properties: + attributes: + additionalProperties: + type: string + description: Additional Attributes to set for + the generated cookie. + type: object + name: + description: |- + Name of the cookie to hash. + If this cookie does not exist in the request, Envoy will generate a cookie and set + the TTL on the response back to the client based on Layer 4 + attributes of the backend endpoint, to ensure that these future requests + go to the same backend endpoint. Make sure to set the TTL field for this case. + type: string + ttl: + description: |- + TTL of the generated cookie if the cookie is not present. This value sets the + Max-Age attribute value. + type: string + required: + - name + type: object + header: + description: Header configures the header hash policy + when the consistent hash type is set to Header. + properties: + name: + description: Name of the header to hash. + type: string + required: + - name + type: object + tableSize: + default: 65537 + description: The table size for consistent hashing, + must be prime number limited to 5000011. + format: int64 + maximum: 5000011 + minimum: 2 + type: integer + type: + description: |- + ConsistentHashType defines the type of input to hash on. Valid Type values are + "SourceIP", + "Header", + "Cookie". + enum: + - SourceIP + - Header + - Cookie + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If consistent hash type is header, the header + field must be set. + rule: 'self.type == ''Header'' ? has(self.header) + : !has(self.header)' + - message: If consistent hash type is cookie, the cookie + field must be set. + rule: 'self.type == ''Cookie'' ? has(self.cookie) + : !has(self.cookie)' + slowStart: + description: |- + SlowStart defines the configuration related to the slow start load balancer policy. + If set, during slow start window, traffic sent to the newly added hosts will gradually increase. + Currently this is only supported for RoundRobin and LeastRequest load balancers + properties: + window: + description: |- + Window defines the duration of the warm up period for newly added host. + During slow start window, traffic sent to the newly added hosts will gradually increase. + Currently only supports linear growth of traffic. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#config-cluster-v3-cluster-slowstartconfig + type: string + required: + - window + type: object + type: + description: |- + Type decides the type of Load Balancer policy. + Valid LoadBalancerType values are + "ConsistentHash", + "LeastRequest", + "Random", + "RoundRobin". + enum: + - ConsistentHash + - LeastRequest + - Random + - RoundRobin + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If LoadBalancer type is consistentHash, consistentHash + field needs to be set. + rule: 'self.type == ''ConsistentHash'' ? has(self.consistentHash) + : !has(self.consistentHash)' + - message: Currently SlowStart is only supported for RoundRobin + and LeastRequest load balancers. + rule: 'self.type in [''Random'', ''ConsistentHash''] ? + !has(self.slowStart) : true ' + proxyProtocol: + description: ProxyProtocol enables the Proxy Protocol when + communicating with the backend. + properties: + version: + description: |- + Version of ProxyProtol + Valid ProxyProtocolVersion values are + "V1" + "V2" + enum: + - V1 + - V2 + type: string + required: + - version + type: object + retry: + description: |- + Retry provides more advanced usage, allowing users to customize the number of retries, retry fallback strategy, and retry triggering conditions. + If not set, retry will be disabled. + properties: + numRetries: + default: 2 + description: NumRetries is the number of retries to + be attempted. Defaults to 2. + format: int32 + minimum: 0 + type: integer + perRetry: + description: PerRetry is the retry policy to be applied + per retry attempt. + properties: + backOff: + description: |- + Backoff is the backoff policy to be applied per retry attempt. gateway uses a fully jittered exponential + back-off algorithm for retries. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#config-http-filters-router-x-envoy-max-retries + properties: + baseInterval: + description: BaseInterval is the base interval + between retries. + format: duration + type: string + maxInterval: + description: |- + MaxInterval is the maximum interval between retries. This parameter is optional, but must be greater than or equal to the base_interval if set. + The default is 10 times the base_interval + format: duration + type: string + type: object + timeout: + description: Timeout is the timeout per retry attempt. + format: duration + type: string + type: object + retryOn: + description: |- + RetryOn specifies the retry trigger condition. + + If not specified, the default is to retry on connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes(503). + properties: + httpStatusCodes: + description: |- + HttpStatusCodes specifies the http status codes to be retried. + The retriable-status-codes trigger must also be configured for these status codes to trigger a retry. + items: + description: HTTPStatus defines the http status + code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + triggers: + description: Triggers specifies the retry trigger + condition(Http/Grpc). + items: + description: TriggerEnum specifies the conditions + that trigger retries. + enum: + - 5xx + - gateway-error + - reset + - connect-failure + - retriable-4xx + - refused-stream + - retriable-status-codes + - cancelled + - deadline-exceeded + - internal + - resource-exhausted + - unavailable + type: string + type: array + type: object + type: object + tcpKeepalive: + description: |- + TcpKeepalive settings associated with the upstream client connection. + Disabled by default. + properties: + idleTime: + description: |- + The duration a connection needs to be idle before keep-alive + probes start being sent. + The duration format is + Defaults to `7200s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + The duration between keep-alive probes. + Defaults to `75s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + probes: + description: |- + The total number of unacknowledged probes to send before deciding + the connection is dead. + Defaults to 9. + format: int32 + type: integer + type: object + timeout: + description: Timeout settings for the backend connections. + properties: + http: + description: Timeout settings for HTTP. + properties: + connectionIdleTimeout: + description: |- + The idle timeout for an HTTP connection. Idle time is defined as a period in which there are no active requests in the connection. + Default: 1 hour. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + maxConnectionDuration: + description: |- + The maximum duration of an HTTP connection. + Default: unlimited. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + requestTimeout: + description: RequestTimeout is the time until which + entire response is received from the upstream. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + tcp: + description: Timeout settings for TCP. + properties: + connectTimeout: + description: |- + The timeout for network connection establishment, including TCP and TLS handshakes. + Default: 10 seconds. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + type: object + type: object + failOpen: + description: |- + FailOpen defines if requests or responses that cannot be processed due to connectivity to the + external processor are terminated or passed-through. + Default: false + type: boolean + messageTimeout: + description: |- + MessageTimeout is the timeout for a response to be returned from the external processor + Default: 200ms + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + metadata: + description: |- + Metadata defines options related to the sending and receiving of dynamic metadata. + These options define which metadata namespaces would be sent to the processor and which dynamic metadata + namespaces the processor would be permitted to emit metadata to. + Users can specify custom namespaces or well-known envoy metadata namespace (such as envoy.filters.http.ext_authz) + documented here: https://www.envoyproxy.io/docs/envoy/latest/configuration/advanced/well_known_dynamic_metadata#well-known-dynamic-metadata + Default: no metadata context is sent or received from the external processor + properties: + accessibleNamespaces: + description: AccessibleNamespaces are metadata namespaces + that are sent to the external processor as context + items: + type: string + type: array + writableNamespaces: + description: WritableNamespaces are metadata namespaces + that the external processor can write to + items: + type: string + maxItems: 8 + type: array + x-kubernetes-validations: + - message: writableNamespaces cannot contain well-known + Envoy HTTP filter namespaces + rule: self.all(f, !f.startsWith('envoy.filters.http')) + type: object + processingMode: + description: |- + ProcessingMode defines how request and response body is processed + Default: header and body are not sent to the external processor + properties: + allowModeOverride: + description: |- + AllowModeOverride allows the external processor to override the processing mode set via the + `mode_override` field in the gRPC response message. This defaults to false. + type: boolean + request: + description: |- + Defines processing mode for requests. If present, request headers are sent. Request body is processed according + to the specified mode. + properties: + attributes: + description: |- + Defines which attributes are sent to the external processor. Envoy Gateway currently + supports only the following attribute prefixes: connection, source, destination, + request, response, upstream and xds.route. + https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes + items: + pattern: ^(connection\.|source\.|destination\.|request\.|response\.|upstream\.|xds\.route_)[a-z_1-9]*$ + type: string + type: array + body: + description: Defines body processing mode + enum: + - Streamed + - Buffered + - BufferedPartial + type: string + type: object + response: + description: |- + Defines processing mode for responses. If present, response headers are sent. Response body is processed according + to the specified mode. + properties: + attributes: + description: |- + Defines which attributes are sent to the external processor. Envoy Gateway currently + supports only the following attribute prefixes: connection, source, destination, + request, response, upstream and xds.route. + https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes + items: + pattern: ^(connection\.|source\.|destination\.|request\.|response\.|upstream\.|xds\.route_)[a-z_1-9]*$ + type: string + type: array + body: + description: Defines body processing mode + enum: + - Streamed + - Buffered + - BufferedPartial + type: string + type: object + type: object + type: object + x-kubernetes-validations: + - message: BackendRefs must be used, backendRef is not supported. + rule: '!has(self.backendRef)' + - message: BackendRefs only supports Service and Backend kind. + rule: 'has(self.backendRefs) ? self.backendRefs.all(f, f.kind + == ''Service'' || f.kind == ''Backend'') : true' + - message: BackendRefs only supports Core and gateway.envoyproxy.io + group. + rule: 'has(self.backendRefs) ? (self.backendRefs.all(f, f.group + == "" || f.group == ''gateway.envoyproxy.io'')) : true' + maxItems: 16 + type: array + lua: + description: |- + Lua is an ordered list of Lua filters + that should be added to the envoy filter chain + items: + description: |- + Lua defines a Lua extension + Only one of Inline or ValueRef must be set + properties: + inline: + description: Inline contains the source code as an inline string. + type: string + type: + default: Inline + description: |- + Type is the type of method to use to read the Lua value. + Valid values are Inline and ValueRef, default is Inline. + enum: + - Inline + - ValueRef + type: string + valueRef: + description: |- + ValueRef has the source code specified as a local object reference. + Only a reference to ConfigMap is supported. + The value of key `lua` in the ConfigMap will be used. + If the key is not found, the first value in the ConfigMap will be used. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" + or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + x-kubernetes-validations: + - message: Only a reference to an object of kind ConfigMap belonging + to default v1 API group is supported. + rule: self.kind == 'ConfigMap' && (self.group == 'v1' || self.group + == '') + required: + - type + type: object + x-kubernetes-validations: + - message: Exactly one of inline or valueRef must be set with correct + type. + rule: (self.type == 'Inline' && has(self.inline) && !has(self.valueRef)) + || (self.type == 'ValueRef' && !has(self.inline) && has(self.valueRef)) + maxItems: 16 + type: array + targetRef: + description: |- + TargetRef is the name of the resource this policy is being attached to. + This policy and the TargetRef MUST be in the same namespace for this + Policy to have effect + + Deprecated: use targetRefs/targetSelectors instead + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + targetRefs: + description: |- + TargetRefs are the names of the Gateway resources this policy + is being attached to. + items: + description: |- + LocalPolicyTargetReferenceWithSectionName identifies an API object to apply a + direct policy to. This should be used as part of Policy resources that can + target single resources. For more information on how this policy attachment + mode works, and a sample Policy resource, refer to the policy attachment + documentation for Gateway API. + + Note: This should only be used for direct policy attachment when references + to SectionName are actually needed. In all other cases, + LocalPolicyTargetReference should be used. + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + type: array + targetSelectors: + description: TargetSelectors allow targeting resources for this policy + based on labels + items: + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group that this selector targets. + Defaults to gateway.networking.k8s.io + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the resource kind that this selector targets. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: MatchLabels are the set of label selectors for + identifying the targeted resource + type: object + required: + - kind + type: object + x-kubernetes-validations: + - message: group must be gateway.networking.k8s.io + rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' + : true ' + type: array + wasm: + description: |- + Wasm is a list of Wasm extensions to be loaded by the Gateway. + Order matters, as the extensions will be loaded in the order they are + defined in this list. + items: + description: |- + Wasm defines a Wasm extension. + + Note: at the moment, Envoy Gateway does not support configuring Wasm runtime. + v8 is used as the VM runtime for the Wasm extensions. + properties: + code: + description: Code is the Wasm code for the extension. + properties: + http: + description: |- + HTTP is the HTTP URL containing the Wasm code. + + Note that the HTTP server must be accessible from the Envoy proxy. + properties: + sha256: + description: |- + SHA256 checksum that will be used to verify the Wasm code. + + If not specified, Envoy Gateway will not verify the downloaded Wasm code. + kubebuilder:validation:Pattern=`^[a-f0-9]{64}$` + type: string + tls: + description: TLS configuration when connecting to the + Wasm code source. + properties: + caCertificateRef: + description: |- + CACertificateRef contains a references to + Kubernetes objects that contain TLS certificates of + the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the Wasm code source. + + Kubernetes ConfigMap and Kubernetes Secret are supported. + Note: The ConfigMap or Secret must be in the same namespace as the EnvoyExtensionPolicy. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For + example "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + required: + - caCertificateRef + type: object + url: + description: URL is the URL containing the Wasm code. + pattern: ^((https?:)(\/\/\/?)([\w]*(?::[\w]*)?@)?([\d\w\.-]+)(?::(\d+))?)?([\/\\\w\.()-]*)?(?:([?][^#]*)?(#.*)?)* + type: string + required: + - url + type: object + image: + description: |- + Image is the OCI image containing the Wasm code. + + Note that the image must be accessible from the Envoy Gateway. + properties: + pullSecretRef: + description: |- + PullSecretRef is a reference to the secret containing the credentials to pull the image. + Only support Kubernetes Secret resource from the same namespace. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example + "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + x-kubernetes-validations: + - message: only support Secret kind. + rule: self.kind == 'Secret' + sha256: + description: |- + SHA256 checksum that will be used to verify the OCI image. + + It must match the digest of the OCI image. + + If not specified, Envoy Gateway will not verify the downloaded OCI image. + kubebuilder:validation:Pattern=`^[a-f0-9]{64}$` + type: string + tls: + description: TLS configuration when connecting to the + Wasm code source. + properties: + caCertificateRef: + description: |- + CACertificateRef contains a references to + Kubernetes objects that contain TLS certificates of + the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the Wasm code source. + + Kubernetes ConfigMap and Kubernetes Secret are supported. + Note: The ConfigMap or Secret must be in the same namespace as the EnvoyExtensionPolicy. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For + example "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + required: + - caCertificateRef + type: object + url: + description: |- + URL is the URL of the OCI image. + URL can be in the format of `registry/image:tag` or `registry/image@sha256:digest`. + type: string + required: + - url + type: object + pullPolicy: + description: |- + PullPolicy is the policy to use when pulling the Wasm module by either the HTTP or Image source. + This field is only applicable when the SHA256 field is not set. + + If not specified, the default policy is IfNotPresent except for OCI images whose tag is latest. + + Note: EG does not update the Wasm module every time an Envoy proxy requests + the Wasm module even if the pull policy is set to Always. + It only updates the Wasm module when the EnvoyExtension resource version changes. + enum: + - IfNotPresent + - Always + type: string + type: + allOf: + - enum: + - HTTP + - Image + - enum: + - HTTP + - Image + - ConfigMap + description: |- + Type is the type of the source of the Wasm code. + Valid WasmCodeSourceType values are "HTTP" or "Image". + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If type is HTTP, http field needs to be set. + rule: 'self.type == ''HTTP'' ? has(self.http) : !has(self.http)' + - message: If type is Image, image field needs to be set. + rule: 'self.type == ''Image'' ? has(self.image) : !has(self.image)' + config: + description: |- + Config is the configuration for the Wasm extension. + This configuration will be passed as a JSON string to the Wasm extension. + x-kubernetes-preserve-unknown-fields: true + env: + description: Env configures the environment for the Wasm extension + properties: + hostKeys: + description: |- + HostKeys is a list of keys for environment variables from the host envoy process + that should be passed into the Wasm VM. This is useful for passing secrets to to Wasm extensions. + items: + type: string + type: array + type: object + failOpen: + default: false + description: |- + FailOpen is a switch used to control the behavior when a fatal error occurs + during the initialization or the execution of the Wasm extension. + If FailOpen is set to true, the system bypasses the Wasm extension and + allows the traffic to pass through. Otherwise, if it is set to false or + not set (defaulting to false), the system blocks the traffic and returns + an HTTP 5xx error. + type: boolean + name: + description: |- + Name is a unique name for this Wasm extension. It is used to identify the + Wasm extension if multiple extensions are handled by the same vm_id and root_id. + It's also used for logging/debugging. + If not specified, EG will generate a unique name for the Wasm extension. + type: string + rootID: + description: |- + RootID is a unique ID for a set of extensions in a VM which will share a + RootContext and Contexts if applicable (e.g., an Wasm HttpFilter and an Wasm AccessLog). + If left blank, all extensions with a blank root_id with the same vm_id will share Context(s). + + Note: RootID must match the root_id parameter used to register the Context in the Wasm code. + type: string + required: + - code + type: object + maxItems: 16 + type: array + type: object + x-kubernetes-validations: + - message: either targetRef or targetRefs must be used + rule: '(has(self.targetRef) && !has(self.targetRefs)) || (!has(self.targetRef) + && has(self.targetRefs)) || (has(self.targetSelectors) && self.targetSelectors.size() + > 0) ' + - message: this policy can only have a targetRef.group of gateway.networking.k8s.io + rule: 'has(self.targetRef) ? self.targetRef.group == ''gateway.networking.k8s.io'' + : true' + - message: this policy can only have a targetRef.kind of Gateway/HTTPRoute/GRPCRoute/TCPRoute/UDPRoute/TLSRoute + rule: 'has(self.targetRef) ? self.targetRef.kind in [''Gateway'', ''HTTPRoute'', + ''GRPCRoute'', ''UDPRoute'', ''TCPRoute'', ''TLSRoute''] : true' + - message: this policy does not yet support the sectionName field + rule: 'has(self.targetRef) ? !has(self.targetRef.sectionName) : true' + - message: this policy can only have a targetRefs[*].group of gateway.networking.k8s.io + rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, ref.group == + ''gateway.networking.k8s.io'') : true ' + - message: this policy can only have a targetRefs[*].kind of Gateway/HTTPRoute/GRPCRoute/TCPRoute/UDPRoute/TLSRoute + rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, ref.kind in [''Gateway'', + ''HTTPRoute'', ''GRPCRoute'', ''UDPRoute'', ''TCPRoute'', ''TLSRoute'']) + : true ' + - message: this policy does not yet support the sectionName field + rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, !has(ref.sectionName)) + : true' + status: + description: Status defines the current status of EnvoyExtensionPolicy. + properties: + ancestors: + description: |- + Ancestors is a list of ancestor resources (usually Gateways) that are + associated with the policy, and the status of the policy with respect to + each ancestor. When this policy attaches to a parent, the controller that + manages the parent and the ancestors MUST add an entry to this list when + the controller first sees the policy and SHOULD update the entry as + appropriate when the relevant ancestor is modified. + + Note that choosing the relevant ancestor is left to the Policy designers; + an important part of Policy design is designing the right object level at + which to namespace this status. + + Note also that implementations MUST ONLY populate ancestor status for + the Ancestor resources they are responsible for. Implementations MUST + use the ControllerName field to uniquely identify the entries in this list + that they are responsible for. + + Note that to achieve this, the list of PolicyAncestorStatus structs + MUST be treated as a map with a composite key, made up of the AncestorRef + and ControllerName fields combined. + + A maximum of 16 ancestors will be represented in this list. An empty list + means the Policy is not relevant for any ancestors. + + If this slice is full, implementations MUST NOT add further entries. + Instead they MUST consider the policy unimplementable and signal that + on any related resources such as the ancestor that would be referenced + here. For example, if this list was full on BackendTLSPolicy, no + additional Gateways would be able to reference the Service targeted by + the BackendTLSPolicy. + items: + description: |- + PolicyAncestorStatus describes the status of a route with respect to an + associated Ancestor. + + Ancestors refer to objects that are either the Target of a policy or above it + in terms of object hierarchy. For example, if a policy targets a Service, the + Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and + the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most + useful object to place Policy status on, so we recommend that implementations + SHOULD use Gateway as the PolicyAncestorStatus object unless the designers + have a _very_ good reason otherwise. + + In the context of policy attachment, the Ancestor is used to distinguish which + resource results in a distinct application of this policy. For example, if a policy + targets a Service, it may have a distinct result per attached Gateway. + + Policies targeting the same resource may have different effects depending on the + ancestors of those resources. For example, different Gateways targeting the same + Service may have different capabilities, especially if they have different underlying + implementations. + + For example, in BackendTLSPolicy, the Policy attaches to a Service that is + used as a backend in a HTTPRoute that is itself attached to a Gateway. + In this case, the relevant object for status is the Gateway, and that is the + ancestor object referred to in this status. + + Note that a parent is also an ancestor, so for objects where the parent is the + relevant object for status, this struct SHOULD still be used. + + This struct is intended to be used in a slice that's effectively a map, + with a composite key made up of the AncestorRef and the ControllerName. + properties: + ancestorRef: + description: |- + AncestorRef corresponds with a ParentRef in the spec that this + PolicyAncestorStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: Conditions describes the status of the Policy with + respect to the given Ancestor. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - controllerName + type: object + maxItems: 16 + type: array + required: + - ancestors + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoypatchpolicies.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 + name: envoypatchpolicies.gateway.envoyproxy.io +spec: + group: gateway.envoyproxy.io + names: + categories: + - envoy-gateway + kind: EnvoyPatchPolicy + listKind: EnvoyPatchPolicyList + plural: envoypatchpolicies + shortNames: + - epp + singular: envoypatchpolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Programmed")].reason + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + EnvoyPatchPolicy allows the user to modify the generated Envoy xDS + resources by Envoy Gateway using this patch API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of EnvoyPatchPolicy. + properties: + jsonPatches: + description: JSONPatch defines the JSONPatch configuration. + items: + description: |- + EnvoyJSONPatchConfig defines the configuration for patching a Envoy xDS Resource + using JSONPatch semantic + properties: + name: + description: Name is the name of the resource + type: string + operation: + description: Patch defines the JSON Patch Operation + properties: + from: + description: |- + From is the source location of the value to be copied or moved. Only valid + for move or copy operations + Refer to https://datatracker.ietf.org/doc/html/rfc6901 for more details. + type: string + jsonPath: + description: |- + JSONPath is a JSONPath expression. Refer to https://datatracker.ietf.org/doc/rfc9535/ for more details. + It produces one or more JSONPointer expressions based on the given JSON document. + If no JSONPointer is found, it will result in an error. + If the 'Path' property is also set, it will be appended to the resulting JSONPointer expressions from the JSONPath evaluation. + This is useful when creating a property that does not yet exist in the JSON document. + The final JSONPointer expressions specifies the locations in the target document/field where the operation will be applied. + type: string + op: + description: Op is the type of operation to perform + enum: + - add + - remove + - replace + - move + - copy + - test + type: string + path: + description: |- + Path is a JSONPointer expression. Refer to https://datatracker.ietf.org/doc/html/rfc6901 for more details. + It specifies the location of the target document/field where the operation will be performed + type: string + value: + description: |- + Value is the new value of the path location. The value is only used by + the `add` and `replace` operations. + x-kubernetes-preserve-unknown-fields: true + required: + - op + type: object + type: + description: Type is the typed URL of the Envoy xDS Resource + enum: + - type.googleapis.com/envoy.config.listener.v3.Listener + - type.googleapis.com/envoy.config.route.v3.RouteConfiguration + - type.googleapis.com/envoy.config.cluster.v3.Cluster + - type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment + - type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret + type: string + required: + - name + - operation + - type + type: object + type: array + priority: + description: |- + Priority of the EnvoyPatchPolicy. + If multiple EnvoyPatchPolicies are applied to the same + TargetRef, they will be applied in the ascending order of + the priority i.e. int32.min has the highest priority and + int32.max has the lowest priority. + Defaults to 0. + format: int32 + type: integer + targetRef: + description: |- + TargetRef is the name of the Gateway API resource this policy + is being attached to. + By default, attaching to Gateway is supported and + when mergeGateways is enabled it should attach to GatewayClass. + This Policy and the TargetRef MUST be in the same namespace + for this Policy to have effect and be applied to the Gateway + TargetRef + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + type: + description: |- + Type decides the type of patch. + Valid EnvoyPatchType values are "JSONPatch". + enum: + - JSONPatch + type: string + required: + - targetRef + - type + type: object + status: + description: Status defines the current status of EnvoyPatchPolicy. + properties: + ancestors: + description: |- + Ancestors is a list of ancestor resources (usually Gateways) that are + associated with the policy, and the status of the policy with respect to + each ancestor. When this policy attaches to a parent, the controller that + manages the parent and the ancestors MUST add an entry to this list when + the controller first sees the policy and SHOULD update the entry as + appropriate when the relevant ancestor is modified. + + Note that choosing the relevant ancestor is left to the Policy designers; + an important part of Policy design is designing the right object level at + which to namespace this status. + + Note also that implementations MUST ONLY populate ancestor status for + the Ancestor resources they are responsible for. Implementations MUST + use the ControllerName field to uniquely identify the entries in this list + that they are responsible for. + + Note that to achieve this, the list of PolicyAncestorStatus structs + MUST be treated as a map with a composite key, made up of the AncestorRef + and ControllerName fields combined. + + A maximum of 16 ancestors will be represented in this list. An empty list + means the Policy is not relevant for any ancestors. + + If this slice is full, implementations MUST NOT add further entries. + Instead they MUST consider the policy unimplementable and signal that + on any related resources such as the ancestor that would be referenced + here. For example, if this list was full on BackendTLSPolicy, no + additional Gateways would be able to reference the Service targeted by + the BackendTLSPolicy. + items: + description: |- + PolicyAncestorStatus describes the status of a route with respect to an + associated Ancestor. + + Ancestors refer to objects that are either the Target of a policy or above it + in terms of object hierarchy. For example, if a policy targets a Service, the + Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and + the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most + useful object to place Policy status on, so we recommend that implementations + SHOULD use Gateway as the PolicyAncestorStatus object unless the designers + have a _very_ good reason otherwise. + + In the context of policy attachment, the Ancestor is used to distinguish which + resource results in a distinct application of this policy. For example, if a policy + targets a Service, it may have a distinct result per attached Gateway. + + Policies targeting the same resource may have different effects depending on the + ancestors of those resources. For example, different Gateways targeting the same + Service may have different capabilities, especially if they have different underlying + implementations. + + For example, in BackendTLSPolicy, the Policy attaches to a Service that is + used as a backend in a HTTPRoute that is itself attached to a Gateway. + In this case, the relevant object for status is the Gateway, and that is the + ancestor object referred to in this status. + + Note that a parent is also an ancestor, so for objects where the parent is the + relevant object for status, this struct SHOULD still be used. + + This struct is intended to be used in a slice that's effectively a map, + with a composite key made up of the AncestorRef and the ControllerName. + properties: + ancestorRef: + description: |- + AncestorRef corresponds with a ParentRef in the spec that this + PolicyAncestorStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: Conditions describes the status of the Policy with + respect to the given Ancestor. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - controllerName + type: object + maxItems: 16 + type: array + required: + - ancestors + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 + name: envoyproxies.gateway.envoyproxy.io +spec: + group: gateway.envoyproxy.io + names: + categories: + - envoy-gateway + kind: EnvoyProxy + listKind: EnvoyProxyList + plural: envoyproxies + shortNames: + - eproxy + singular: envoyproxy + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: EnvoyProxy is the schema for the envoyproxies API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: EnvoyProxySpec defines the desired state of EnvoyProxy. + properties: + backendTLS: + description: |- + BackendTLS is the TLS configuration for the Envoy proxy to use when connecting to backends. + These settings are applied on backends for which TLS policies are specified. + properties: + alpnProtocols: + description: |- + ALPNProtocols supplies the list of ALPN protocols that should be + exposed by the listener or used by the proxy to connect to the backend. + Defaults: + 1. HTTPS Routes: h2 and http/1.1 are enabled in listener context. + 2. Other Routes: ALPN is disabled. + 3. Backends: proxy uses the appropriate ALPN options for the backend protocol. + When an empty list is provided, the ALPN TLS extension is disabled. + Supported values are: + - http/1.0 + - http/1.1 + - h2 + items: + description: ALPNProtocol specifies the protocol to be negotiated + using ALPN + enum: + - http/1.0 + - http/1.1 + - h2 + type: string + type: array + ciphers: + description: |- + Ciphers specifies the set of cipher suites supported when + negotiating TLS 1.0 - 1.2. This setting has no effect for TLS 1.3. + In non-FIPS Envoy Proxy builds the default cipher list is: + - [ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305] + - [ECDHE-RSA-AES128-GCM-SHA256|ECDHE-RSA-CHACHA20-POLY1305] + - ECDHE-ECDSA-AES256-GCM-SHA384 + - ECDHE-RSA-AES256-GCM-SHA384 + In builds using BoringSSL FIPS the default cipher list is: + - ECDHE-ECDSA-AES128-GCM-SHA256 + - ECDHE-RSA-AES128-GCM-SHA256 + - ECDHE-ECDSA-AES256-GCM-SHA384 + - ECDHE-RSA-AES256-GCM-SHA384 + items: + type: string + type: array + clientCertificateRef: + description: |- + ClientCertificateRef defines the reference to a Kubernetes Secret that contains + the client certificate and private key for Envoy to use when connecting to + backend services and external services, such as ExtAuth, ALS, OpenTelemetry, etc. + This secret should be located within the same namespace as the Envoy proxy resource that references it. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + ecdhCurves: + description: |- + ECDHCurves specifies the set of supported ECDH curves. + In non-FIPS Envoy Proxy builds the default curves are: + - X25519 + - P-256 + In builds using BoringSSL FIPS the default curve is: + - P-256 + items: + type: string + type: array + maxVersion: + description: |- + Max specifies the maximal TLS protocol version to allow + The default is TLS 1.3 if this is not specified. + enum: + - Auto + - "1.0" + - "1.1" + - "1.2" + - "1.3" + type: string + minVersion: + description: |- + Min specifies the minimal TLS protocol version to allow. + The default is TLS 1.2 if this is not specified. + enum: + - Auto + - "1.0" + - "1.1" + - "1.2" + - "1.3" + type: string + signatureAlgorithms: + description: |- + SignatureAlgorithms specifies which signature algorithms the listener should + support. + items: + type: string + type: array + type: object + x-kubernetes-validations: + - message: setting ciphers has no effect if the minimum possible TLS + version is 1.3 + rule: 'has(self.minVersion) && self.minVersion == ''1.3'' ? !has(self.ciphers) + : true' + - message: minVersion must be smaller or equal to maxVersion + rule: 'has(self.minVersion) && has(self.maxVersion) ? {"Auto":0,"1.0":1,"1.1":2,"1.2":3,"1.3":4}[self.minVersion] + <= {"1.0":1,"1.1":2,"1.2":3,"1.3":4,"Auto":5}[self.maxVersion] + : !has(self.minVersion) && has(self.maxVersion) ? 3 <= {"1.0":1,"1.1":2,"1.2":3,"1.3":4,"Auto":5}[self.maxVersion] + : true' + bootstrap: + description: |- + 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. + properties: + jsonPatches: + description: |- + JSONPatches is an array of JSONPatches to be applied to the default bootstrap. Patches are + applied in the order in which they are defined. + items: + description: |- + JSONPatchOperation defines the JSON Patch Operation as defined in + https://datatracker.ietf.org/doc/html/rfc6902 + properties: + from: + description: |- + From is the source location of the value to be copied or moved. Only valid + for move or copy operations + Refer to https://datatracker.ietf.org/doc/html/rfc6901 for more details. + type: string + jsonPath: + description: |- + JSONPath is a JSONPath expression. Refer to https://datatracker.ietf.org/doc/rfc9535/ for more details. + It produces one or more JSONPointer expressions based on the given JSON document. + If no JSONPointer is found, it will result in an error. + If the 'Path' property is also set, it will be appended to the resulting JSONPointer expressions from the JSONPath evaluation. + This is useful when creating a property that does not yet exist in the JSON document. + The final JSONPointer expressions specifies the locations in the target document/field where the operation will be applied. + type: string + op: + description: Op is the type of operation to perform + enum: + - add + - remove + - replace + - move + - copy + - test + type: string + path: + description: |- + Path is a JSONPointer expression. Refer to https://datatracker.ietf.org/doc/html/rfc6901 for more details. + It specifies the location of the target document/field where the operation will be performed + type: string + value: + description: |- + Value is the new value of the path location. The value is only used by + the `add` and `replace` operations. + x-kubernetes-preserve-unknown-fields: true + required: + - op + type: object + type: array + type: + default: Replace + description: |- + Type is the type of the bootstrap configuration, it should be either **Replace**, **Merge**, or **JSONPatch**. + If unspecified, it defaults to Replace. + enum: + - Merge + - Replace + - JSONPatch + type: string + value: + description: Value is a YAML string of the bootstrap. + type: string + type: object + x-kubernetes-validations: + - message: provided bootstrap patch doesn't match the configured patch + type + rule: 'self.type == ''JSONPatch'' ? self.jsonPatches.size() > 0 + : has(self.value)' + concurrency: + description: |- + Concurrency defines the number of worker threads to run. If unset, it defaults to + the number of cpuset threads on the platform. + format: int32 + type: integer + extraArgs: + description: |- + ExtraArgs defines additional command line options that are provided to Envoy. + More info: https://www.envoyproxy.io/docs/envoy/latest/operations/cli#command-line-options + Note: some command line options are used internally(e.g. --log-level) so they cannot be provided here. + items: + type: string + type: array + filterOrder: + description: |- + FilterOrder defines the order of filters in the Envoy proxy's HTTP filter chain. + The FilterPosition in the list will be applied in the order they are defined. + If unspecified, the default filter order is applied. + Default filter order is: + + - envoy.filters.http.health_check + + - envoy.filters.http.fault + + - envoy.filters.http.cors + + - envoy.filters.http.ext_authz + + - envoy.filters.http.basic_auth + + - envoy.filters.http.oauth2 + + - envoy.filters.http.jwt_authn + + - envoy.filters.http.stateful_session + + - envoy.filters.http.lua + + - envoy.filters.http.ext_proc + + - envoy.filters.http.wasm + + - envoy.filters.http.rbac + + - envoy.filters.http.local_ratelimit + + - envoy.filters.http.ratelimit + + - envoy.filters.http.custom_response + + - envoy.filters.http.router + + Note: "envoy.filters.http.router" cannot be reordered, it's always the last filter in the chain. + items: + description: FilterPosition defines the position of an Envoy HTTP + filter in the filter chain. + properties: + after: + description: |- + After defines the filter that should come after the filter. + Only one of Before or After must be set. + enum: + - envoy.filters.http.health_check + - envoy.filters.http.fault + - envoy.filters.http.cors + - envoy.filters.http.ext_authz + - envoy.filters.http.api_key_auth + - envoy.filters.http.basic_auth + - envoy.filters.http.oauth2 + - envoy.filters.http.jwt_authn + - envoy.filters.http.stateful_session + - envoy.filters.http.lua + - envoy.filters.http.ext_proc + - envoy.filters.http.wasm + - envoy.filters.http.rbac + - envoy.filters.http.local_ratelimit + - envoy.filters.http.ratelimit + - envoy.filters.http.custom_response + - envoy.filters.http.compressor + type: string + before: + description: |- + Before defines the filter that should come before the filter. + Only one of Before or After must be set. + enum: + - envoy.filters.http.health_check + - envoy.filters.http.fault + - envoy.filters.http.cors + - envoy.filters.http.ext_authz + - envoy.filters.http.api_key_auth + - envoy.filters.http.basic_auth + - envoy.filters.http.oauth2 + - envoy.filters.http.jwt_authn + - envoy.filters.http.stateful_session + - envoy.filters.http.lua + - envoy.filters.http.ext_proc + - envoy.filters.http.wasm + - envoy.filters.http.rbac + - envoy.filters.http.local_ratelimit + - envoy.filters.http.ratelimit + - envoy.filters.http.custom_response + - envoy.filters.http.compressor + type: string + name: + description: Name of the filter. + enum: + - envoy.filters.http.health_check + - envoy.filters.http.fault + - envoy.filters.http.cors + - envoy.filters.http.ext_authz + - envoy.filters.http.api_key_auth + - envoy.filters.http.basic_auth + - envoy.filters.http.oauth2 + - envoy.filters.http.jwt_authn + - envoy.filters.http.stateful_session + - envoy.filters.http.lua + - envoy.filters.http.ext_proc + - envoy.filters.http.wasm + - envoy.filters.http.rbac + - envoy.filters.http.local_ratelimit + - envoy.filters.http.ratelimit + - envoy.filters.http.custom_response + - envoy.filters.http.compressor + type: string + required: + - name + type: object + x-kubernetes-validations: + - message: one of before or after must be specified + rule: (has(self.before) || has(self.after)) + - message: only one of before or after can be specified + rule: (has(self.before) && !has(self.after)) || (!has(self.before) + && has(self.after)) + type: array + ipFamily: + description: |- + IPFamily specifies the IP family for the EnvoyProxy fleet. + This setting only affects the Gateway listener port and does not impact + other aspects of the Envoy proxy configuration. + If not specified, the system will operate as follows: + - It defaults to IPv4 only. + - IPv6 and dual-stack environments are not supported in this default configuration. + Note: To enable IPv6 or dual-stack functionality, explicit configuration is required. + enum: + - IPv4 + - IPv6 + - DualStack + type: string + logging: + default: + level: + default: warn + description: Logging defines logging parameters for managed proxies. + properties: + level: + additionalProperties: + description: LogLevel defines a log level for Envoy Gateway + and EnvoyProxy system logs. + enum: + - trace + - debug + - info + - warn + - error + type: string + default: + default: warn + description: |- + 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 "default: warn". + type: object + type: object + mergeGateways: + description: |- + MergeGateways defines if Gateway resources should be merged onto the same Envoy Proxy Infrastructure. + Setting this field to true would merge all Gateway Listeners under the parent Gateway Class. + This means that the port, protocol and hostname tuple must be unique for every listener. + If a duplicate listener is detected, the newer listener (based on timestamp) will be rejected and its status will be updated with a "Accepted=False" condition. + type: boolean + preserveRouteOrder: + description: |- + PreserveRouteOrder determines if the order of matching for HTTPRoutes is determined by Gateway-API + specification (https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteRule) + or preserves the order defined by users in the HTTPRoute's HTTPRouteRule list. + Default: False + type: boolean + provider: + description: |- + Provider defines the desired resource provider and provider-specific configuration. + If unspecified, the "Kubernetes" resource provider is used with default configuration + parameters. + properties: + kubernetes: + description: |- + 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. + properties: + envoyDaemonSet: + description: |- + EnvoyDaemonSet defines the desired state of the Envoy daemonset resource. + Disabled by default, a deployment resource is used instead to provision the Envoy Proxy fleet + properties: + container: + description: Container defines the desired specification + of main container. + properties: + env: + description: List of environment variables to set + in the container. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. + Must be a C_IDENTIFIER. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: |- + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + properties: + apiVersion: + description: Version of the schema the + FieldPath is written in terms of, + defaults to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + properties: + containerName: + description: 'Container name: required + for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults + to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to + select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in + the pod's namespace + properties: + key: + description: The key of the secret to + select from. Must be a valid secret + key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + image: + description: Image specifies the EnvoyProxy container + image to be used, instead of the default image. + type: string + resources: + description: |- + Resources required by this container. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry + in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: |- + 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/ + properties: + allowPrivilegeEscalation: + description: |- + AllowPrivilegeEscalation controls whether a process can gain more + privileges than its parent process. This bool directly controls if + the no_new_privs flag will be set on the container process. + AllowPrivilegeEscalation is true always when the container is: + 1) run as Privileged + 2) has CAP_SYS_ADMIN + Note that this field cannot be set when spec.os.name is windows. + type: boolean + appArmorProfile: + description: |- + appArmorProfile is the AppArmor options to use by this container. If set, this profile + overrides the pod's appArmorProfile. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile loaded on the node that should be used. + The profile must be preconfigured on the node to work. + Must match the loaded name of the profile. + Must be set if and only if type is "Localhost". + type: string + type: + description: |- + type indicates which kind of AppArmor profile will be applied. + Valid options are: + Localhost - a profile pre-loaded on the node. + RuntimeDefault - the container runtime's default profile. + Unconfined - no AppArmor enforcement. + type: string + required: + - type + type: object + capabilities: + description: |- + The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the container runtime. + Note that this field cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX + capabilities type + type: string + type: array + x-kubernetes-list-type: atomic + drop: + description: Removed capabilities + items: + description: Capability represent POSIX + capabilities type + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + description: |- + Run container in privileged mode. + Processes in privileged containers are essentially equivalent to root on the host. + Defaults to false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: |- + procMount denotes the type of proc mount to use for the containers. + The default value is Default which uses the container runtime defaults for + readonly paths and masked paths. + This requires the ProcMountType feature flag to be enabled. + Note that this field cannot be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: |- + Whether this container has a read-only root filesystem. + Default is false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label + that applies to the container. + type: string + role: + description: Role is a SELinux role label + that applies to the container. + type: string + type: + description: Type is a SELinux type label + that applies to the container. + type: string + user: + description: User is a SELinux user label + that applies to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by this container. If seccomp options are + provided at both the pod & container level, the container options + override the pod options. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + windowsOptions: + description: |- + The Windows specific settings applied to all containers. + If unspecified, the options from the PodSecurityContext will be used. + If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: |- + GMSACredentialSpec is where the GMSA admission webhook + (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the + GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the + name of the GMSA credential spec to use. + type: string + hostProcess: + description: |- + HostProcess determines if a container should be run as a 'Host Process' container. + All of a Pod's containers must have the same effective HostProcess value + (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). + In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: |- + The UserName in Windows to run the entrypoint of the container process. + Defaults to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + volumeMounts: + description: |- + VolumeMounts are volumes to mount into the container's filesystem. + Cannot be updated. + items: + description: VolumeMount describes a mounting of + a Volume within a container. + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + mountPropagation determines how mounts are propagated from the host + to container and the other way around. + When not set, MountPropagationNone is used. + This field is beta in 1.10. + When RecursiveReadOnly is set to IfPossible or to Enabled, MountPropagation must be None or unspecified + (which defaults to None). + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + recursiveReadOnly: + description: |- + RecursiveReadOnly specifies whether read-only mounts should be handled + recursively. + + If ReadOnly is false, this field has no meaning and must be unspecified. + + If ReadOnly is true, and this field is set to Disabled, the mount is not made + recursively read-only. If this field is set to IfPossible, the mount is made + recursively read-only, if it is supported by the container runtime. If this + field is set to Enabled, the mount is made recursively read-only if it is + supported by the container runtime, otherwise the pod will not be started and + an error will be generated to indicate the reason. + + If this field is set to IfPossible or Enabled, MountPropagation must be set to + None (or be unspecified, which defaults to None). + + If this field is not specified, it is treated as an equivalent of Disabled. + type: string + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: |- + Expanded path within the volume from which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + Defaults to "" (volume's root). + SubPathExpr and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + type: object + name: + description: |- + Name of the daemonSet. + When unset, this defaults to an autogenerated name. + type: string + patch: + description: Patch defines how to perform the patch operation + to daemonset + properties: + type: + description: |- + Type is the type of merge operation to perform + + By default, StrategicMerge is used as the patch type. + type: string + value: + description: Object contains the raw configuration + for merged object + x-kubernetes-preserve-unknown-fields: true + required: + - value + type: object + pod: + description: Pod defines the desired specification of + pod. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + 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. + If it's null, this PodAffinityTerm matches with no 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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. + If it's null, this PodAffinityTerm matches with no 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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. + If it's null, this PodAffinityTerm matches with no 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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. + If it's null, this PodAffinityTerm matches with no 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + type: object + type: object + annotations: + additionalProperties: + type: string + description: |- + Annotations are the annotations that should be appended to the pods. + By default, no pod annotations are appended. + type: object + imagePullSecrets: + description: |- + ImagePullSecrets is an optional list of references to secrets + in the same namespace to use for pulling any of the images used by this PodSpec. + If specified, these secrets will be passed to individual puller implementations for them to use. + More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + labels: + additionalProperties: + type: string + description: |- + Labels are the additional labels that should be tagged to the pods. + By default, no additional pod labels are tagged. + type: object + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is a selector which must be true for the pod to fit on a node. + Selector which must match a node's labels for the pod to be scheduled on that node. + More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + type: object + securityContext: + description: |- + SecurityContext holds pod-level security attributes and common container settings. + Optional: Defaults to empty. See type description for default values of each field. + properties: + appArmorProfile: + description: |- + appArmorProfile is the AppArmor options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile loaded on the node that should be used. + The profile must be preconfigured on the node to work. + Must match the loaded name of the profile. + Must be set if and only if type is "Localhost". + type: string + type: + description: |- + type indicates which kind of AppArmor profile will be applied. + Valid options are: + Localhost - a profile pre-loaded on the node. + RuntimeDefault - the container runtime's default profile. + Unconfined - no AppArmor enforcement. + type: string + required: + - type + type: object + fsGroup: + description: |- + A special supplemental group that applies to all containers in a pod. + Some volume types allow the Kubelet to change the ownership of that volume + to be owned by the pod: + + 1. The owning GID will be the FSGroup + 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + 3. The permission bits are OR'd with rw-rw---- + + If unset, the Kubelet will not modify the ownership and permissions of any volume. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + fsGroupChangePolicy: + description: |- + fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + before being exposed inside Pod. This field will only apply to + volume types which support fsGroup based ownership(and permissions). + It will have no effect on ephemeral volume types such as: secret, configmaps + and emptydir. + Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + Note that this field cannot be set when spec.os.name is windows. + type: string + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxChangePolicy: + description: |- + seLinuxChangePolicy defines how the container's SELinux label is applied to all volumes used by the Pod. + It has no effect on nodes that do not support SELinux or to volumes does not support SELinux. + Valid values are "MountOption" and "Recursive". + + "Recursive" means relabeling of all files on all Pod volumes by the container runtime. + This may be slow for large volumes, but allows mixing privileged and unprivileged Pods sharing the same volume on the same node. + + "MountOption" mounts all eligible Pod volumes with `-o context` mount option. + This requires all Pods that share the same volume to use the same SELinux label. + It is not possible to share the same volume among privileged and unprivileged Pods. + Eligible volumes are in-tree FibreChannel and iSCSI volumes, and all CSI volumes + whose CSI driver announces SELinux support by setting spec.seLinuxMount: true in their + CSIDriver instance. Other volumes are always re-labelled recursively. + "MountOption" value is allowed only when SELinuxMount feature gate is enabled. + + If not specified and SELinuxMount feature gate is enabled, "MountOption" is used. + If not specified and SELinuxMount feature gate is disabled, "MountOption" is used for ReadWriteOncePod volumes + and "Recursive" for all other volumes. + + This field affects only Pods that have SELinux label set, either in PodSecurityContext or in SecurityContext of all containers. + + All Pods that use the same volume should use the same seLinuxChangePolicy, otherwise some pods can get stuck in ContainerCreating state. + Note that this field cannot be set when spec.os.name is windows. + type: string + seLinuxOptions: + description: |- + The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in SecurityContext. If set in + both SecurityContext and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label + that applies to the container. + type: string + role: + description: Role is a SELinux role label + that applies to the container. + type: string + type: + description: Type is a SELinux type label + that applies to the container. + type: string + user: + description: User is a SELinux user label + that applies to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + supplementalGroups: + description: |- + A list of groups applied to the first process run in each container, in + addition to the container's primary GID and fsGroup (if specified). If + the SupplementalGroupsPolicy feature is enabled, the + supplementalGroupsPolicy field determines whether these are in addition + to or instead of any group memberships defined in the container image. + If unspecified, no additional groups are added, though group memberships + defined in the container image may still be used, depending on the + supplementalGroupsPolicy field. + Note that this field cannot be set when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + description: |- + Defines how supplemental groups of the first container processes are calculated. + Valid values are "Merge" and "Strict". If not specified, "Merge" is used. + (Alpha) Using the field requires the SupplementalGroupsPolicy feature gate to be enabled + and the container runtime must implement support for this feature. + Note that this field cannot be set when spec.os.name is windows. + type: string + sysctls: + description: |- + Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + sysctls (by the container runtime) might fail to launch. + Note that this field cannot be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter + to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: + description: |- + The Windows specific settings applied to all containers. + If unspecified, the options within a container's SecurityContext will be used. + If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: |- + GMSACredentialSpec is where the GMSA admission webhook + (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the + GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the + name of the GMSA credential spec to use. + type: string + hostProcess: + description: |- + HostProcess determines if a container should be run as a 'Host Process' container. + All of a Pod's containers must have the same effective HostProcess value + (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). + In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: |- + The UserName in Windows to run the entrypoint of the container process. + Defaults to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + 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 + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: TopologySpreadConstraint specifies + how to spread matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + volumes: + description: |- + Volumes that can be mounted by containers belonging to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes + items: + description: Volume represents a named volume in + a pod that may be accessed by any container in + the pod. + properties: + awsElasticBlockStore: + description: |- + awsElasticBlockStore represents an AWS Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + Deprecated: AWSElasticBlockStore is deprecated. All operations for the in-tree + awsElasticBlockStore type are redirected to the ebs.csi.aws.com CSI driver. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + format: int32 + type: integer + readOnly: + description: |- + readOnly value true will force the readOnly setting in VolumeMounts. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: boolean + volumeID: + description: |- + volumeID is unique ID of the persistent disk resource in AWS (Amazon EBS volume). + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + required: + - volumeID + type: object + azureDisk: + description: |- + azureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. + Deprecated: AzureDisk is deprecated. All operations for the in-tree azureDisk type + are redirected to the disk.csi.azure.com CSI driver. + properties: + cachingMode: + description: 'cachingMode is the Host Caching + mode: None, Read Only, Read Write.' + type: string + diskName: + description: diskName is the Name of the + data disk in the blob storage + type: string + diskURI: + description: diskURI is the URI of data + disk in the blob storage + type: string + fsType: + default: ext4 + description: |- + fsType is Filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + kind: + description: 'kind expected values are Shared: + multiple blob disks per storage account Dedicated: + single blob disk per storage account Managed: + azure managed data disk (only in managed + availability set). defaults to shared' + type: string + readOnly: + default: false + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: |- + azureFile represents an Azure File Service mount on the host and bind mount to the pod. + Deprecated: AzureFile is deprecated. All operations for the in-tree azureFile type + are redirected to the file.csi.azure.com CSI driver. + properties: + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: secretName is the name of + secret that contains Azure Storage Account + Name and Key + type: string + shareName: + description: shareName is the azure share + Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: |- + cephFS represents a Ceph FS mount on the host that shares a pod's lifetime. + Deprecated: CephFS is deprecated and the in-tree cephfs type is no longer supported. + properties: + monitors: + description: |- + monitors is Required: Monitors is a collection of Ceph monitors + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + items: + type: string + type: array + x-kubernetes-list-type: atomic + path: + description: 'path is Optional: Used as + the mounted root, rather than the full + Ceph tree, default is /' + type: string + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: boolean + secretFile: + description: |- + secretFile is Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + secretRef: + description: |- + secretRef is Optional: SecretRef is reference to the authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is optional: User is the rados user name, default is admin + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + required: + - monitors + type: object + cinder: + description: |- + cinder represents a cinder volume attached and mounted on kubelets host machine. + Deprecated: Cinder is deprecated. All operations for the in-tree cinder type + are redirected to the cinder.csi.openstack.org CSI driver. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: boolean + secretRef: + description: |- + secretRef is optional: points to a secret object containing parameters used to connect + to OpenStack. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + description: |- + volumeID used to identify the volume in cinder. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + required: + - volumeID + type: object + configMap: + description: configMap represents a configMap + that should populate this volume + properties: + defaultMode: + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional specify whether the + ConfigMap or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + description: csi (Container Storage Interface) + represents ephemeral storage that is handled + by certain external CSI drivers. + properties: + driver: + description: |- + driver is the name of the CSI driver that handles this volume. + Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: |- + fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the associated CSI driver + which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: |- + nodePublishSecretRef is a reference to the secret object containing + sensitive information to pass to the CSI driver to complete the CSI + NodePublishVolume and NodeUnpublishVolume calls. + This field is optional, and may be empty if no secret is required. If the + secret object contains more than one secret, all secret references are passed. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + description: |- + readOnly specifies a read-only configuration for the volume. + Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: |- + volumeAttributes stores driver-specific properties that are passed to the CSI + driver. Consult your driver's documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: downwardAPI represents downward + API about the pod that should populate this + volume + properties: + defaultMode: + description: |- + Optional: mode bits to use on created files by default. Must be a + Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: Items is a list of downward + API volume file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects a + field of the pod: only annotations, + labels, name, namespace and uid + are supported.' + properties: + apiVersion: + description: Version of the schema + the FieldPath is written in + terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field + to select in the specified API + version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file to + be created. Must not be absolute + or contain the ''..'' path. Must + be utf-8 encoded. The first item + of the relative path must not start + with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: + required for volumes, optional + for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output + format of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + emptyDir: + description: |- + emptyDir represents a temporary directory that shares a pod's lifetime. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + properties: + medium: + description: |- + medium represents what type of storage medium should back this directory. + The default is "" which means to use the node's default medium. + Must be an empty string (default) or Memory. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: |- + sizeLimit is the total amount of local storage required for this EmptyDir volume. + The size limit is also applicable for memory medium. + The maximum usage on memory medium EmptyDir would be the minimum value between + the SizeLimit specified here and the sum of memory limits of all containers in a pod. + The default is nil which means that the limit is undefined. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + description: |- + ephemeral represents a volume that is handled by a cluster storage driver. + The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, + and deleted when the pod is removed. + + Use this if: + a) the volume is only needed while the pod runs, + b) features of normal volumes like restoring from snapshot or capacity + tracking are needed, + c) the storage driver is specified through a storage class, and + d) the storage driver supports dynamic volume provisioning through + a PersistentVolumeClaim (see EphemeralVolumeSource for more + information on the connection between this volume type + and PersistentVolumeClaim). + + Use PersistentVolumeClaim or one of the vendor-specific + APIs for volumes that persist for longer than the lifecycle + of an individual pod. + + Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to + be used that way - see the documentation of the driver for + more information. + + A pod can use both types of ephemeral volumes and + persistent volumes at the same time. + properties: + volumeClaimTemplate: + description: |- + Will be used to create a stand-alone PVC to provision the volume. + The pod in which this EphemeralVolumeSource is embedded will be the + owner of the PVC, i.e. the PVC will be deleted together with the + pod. The name of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` array + entry. Pod validation will reject the pod if the concatenated name + is not valid for a PVC (for example, too long). + + An existing PVC with that name that is not owned by the pod + will *not* be used for the pod to avoid using an unrelated + volume by mistake. Starting the pod is then blocked until + the unrelated PVC is removed. If such a pre-created PVC is + meant to be used by the pod, the PVC has to updated with an + owner reference to the pod once the pod exists. Normally + this should not be necessary, but it may be useful when + manually reconstructing a broken cluster. + + This field is read-only and no changes will be made by Kubernetes + to the PVC after it has been created. + + Required, must not be nil. + properties: + metadata: + description: |- + May contain labels and annotations that will be copied into the PVC + when creating it. No other fields are allowed and will be rejected during + validation. + type: object + spec: + description: |- + The specification for the PersistentVolumeClaim. The entire content is + copied unchanged into the PVC that gets created from this + template. The same fields as in a PersistentVolumeClaim + are also valid here. + properties: + accessModes: + description: |- + accessModes contains the desired access modes the volume should have. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 + items: + type: string + type: array + x-kubernetes-list-type: atomic + dataSource: + description: |- + dataSource field can be used to specify either: + * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) + If the provisioner or an external controller can support the specified data source, + it will create a new volume based on the contents of the specified data source. + When the AnyVolumeDataSource feature gate is enabled, dataSource contents will be copied to dataSourceRef, + and dataSourceRef contents will be copied to dataSource when dataSourceRef.namespace is not specified. + If the namespace is specified, then dataSourceRef will not be copied to dataSource. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type + of resource being referenced + type: string + name: + description: Name is the name + of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + description: |- + dataSourceRef specifies the object from which to populate the volume with data, if a non-empty + volume is desired. This may be any object from a non-empty API group (non + core object) or a PersistentVolumeClaim object. + When this field is specified, volume binding will only succeed if the type of + the specified object matches some installed volume populator or dynamic + provisioner. + This field will replace the functionality of the dataSource field and as such + if both fields are non-empty, they must have the same value. For backwards + compatibility, when namespace isn't specified in dataSourceRef, + both fields (dataSource and dataSourceRef) will be set to the same + value automatically if one of them is empty and the other is non-empty. + When namespace is specified in dataSourceRef, + dataSource isn't set to the same value and must be empty. + There are three important differences between dataSource and dataSourceRef: + * While dataSource only allows two specific types of objects, dataSourceRef + allows any non-core object, as well as PersistentVolumeClaim objects. + * While dataSource ignores disallowed values (dropping them), dataSourceRef + preserves all values, and generates an error if a disallowed value is + specified. + * While dataSource only allows local objects, dataSourceRef allows objects + in any namespaces. + (Beta) Using this field requires the AnyVolumeDataSource feature gate to be enabled. + (Alpha) Using the namespace field of dataSourceRef requires the CrossNamespaceVolumeDataSource feature gate to be enabled. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type + of resource being referenced + type: string + name: + description: Name is the name + of resource being referenced + type: string + namespace: + description: |- + Namespace is the namespace of resource being referenced + Note that when a namespace is specified, a gateway.networking.k8s.io/ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. + (Alpha) This field requires the CrossNamespaceVolumeDataSource feature gate to be enabled. + type: string + required: + - kind + - name + type: object + resources: + description: |- + resources represents the minimum resources the volume should have. + If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements + that are lower than previous value but must still be higher than capacity recorded in the + status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + selector: + description: selector is a label + query over volumes to consider + for binding. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + storageClassName: + description: |- + storageClassName is the name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 + type: string + volumeAttributesClassName: + description: |- + volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim. + If specified, the CSI driver will create or update the volume with the attributes defined + in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName, + it can be changed after the claim is created. An empty string value means that no VolumeAttributesClass + will be applied to the claim but it's not allowed to reset this field to empty string once it is set. + If unspecified and the PersistentVolumeClaim is unbound, the default VolumeAttributesClass + will be set by the persistentvolume controller if it exists. + If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be + set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource + exists. + More info: https://kubernetes.io/docs/concepts/storage/volume-attributes-classes/ + (Beta) Using this field requires the VolumeAttributesClass feature gate to be enabled (off by default). + type: string + volumeMode: + description: |- + volumeMode defines what type of volume is required by the claim. + Value of Filesystem is implied when not included in claim spec. + type: string + volumeName: + description: volumeName is the binding + reference to the PersistentVolume + backing this claim. + type: string + type: object + required: + - spec + type: object + type: object + fc: + description: fc represents a Fibre Channel resource + that is attached to a kubelet's host machine + and then exposed to the pod. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + lun: + description: 'lun is Optional: FC target + lun number' + format: int32 + type: integer + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + targetWWNs: + description: 'targetWWNs is Optional: FC + target worldwide names (WWNs)' + items: + type: string + type: array + x-kubernetes-list-type: atomic + wwids: + description: |- + wwids Optional: FC volume world wide identifiers (wwids) + Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + flexVolume: + description: |- + flexVolume represents a generic volume resource that is + provisioned/attached using an exec based plugin. + Deprecated: FlexVolume is deprecated. Consider using a CSIDriver instead. + properties: + driver: + description: driver is the name of the driver + to use for this volume. + type: string + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + type: string + options: + additionalProperties: + type: string + description: 'options is Optional: this + field holds extra command options if any.' + type: object + readOnly: + description: |- + readOnly is Optional: defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef is Optional: secretRef is reference to the secret object containing + sensitive information to pass to the plugin scripts. This may be + empty if no secret object is specified. If the secret object + contains more than one secret, all secrets are passed to the plugin + scripts. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + description: |- + flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running. + Deprecated: Flocker is deprecated and the in-tree flocker type is no longer supported. + properties: + datasetName: + description: |- + datasetName is Name of the dataset stored as metadata -> name on the dataset for Flocker + should be considered as deprecated + type: string + datasetUUID: + description: datasetUUID is the UUID of + the dataset. This is unique identifier + of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: |- + gcePersistentDisk represents a GCE Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + Deprecated: GCEPersistentDisk is deprecated. All operations for the in-tree + gcePersistentDisk type are redirected to the pd.csi.storage.gke.io CSI driver. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + properties: + fsType: + description: |- + fsType is filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + format: int32 + type: integer + pdName: + description: |- + pdName is unique name of the PD resource in GCE. Used to identify the disk in GCE. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: boolean + required: + - pdName + type: object + gitRepo: + description: |- + gitRepo represents a git repository at a particular revision. + Deprecated: GitRepo is deprecated. To provision a container with a git repo, mount an + EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir + into the Pod's container. + properties: + directory: + description: |- + directory is the target directory name. + Must not contain or start with '..'. If '.' is supplied, the volume directory will be the + git repository. Otherwise, if specified, the volume will contain the git repository in + the subdirectory with the given name. + type: string + repository: + description: repository is the URL + type: string + revision: + description: revision is the commit hash + for the specified revision. + type: string + required: + - repository + type: object + glusterfs: + description: |- + glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. + Deprecated: Glusterfs is deprecated and the in-tree glusterfs type is no longer supported. + More info: https://examples.k8s.io/volumes/glusterfs/README.md + properties: + endpoints: + description: |- + endpoints is the endpoint name that details Glusterfs topology. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + path: + description: |- + path is the Glusterfs volume path. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + readOnly: + description: |- + readOnly here will force the Glusterfs volume to be mounted with read-only permissions. + Defaults to false. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: |- + hostPath represents a pre-existing file or directory on the host + machine that is directly exposed to the container. This is generally + used for system agents or other privileged things that are allowed + to see the host machine. Most containers will NOT need this. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + properties: + path: + description: |- + path of the directory on the host. + If the path is a symlink, it will follow the link to the real path. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + type: + description: |- + type for HostPath Volume + Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + required: + - path + type: object + image: + description: |- + image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. + The volume is resolved at pod startup depending on which PullPolicy value is provided: + + - Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. + - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. + - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. + + The volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. + A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. + The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. + The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. + The volume will be mounted read-only (ro) and non-executable files (noexec). + Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. + The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type. + properties: + pullPolicy: + description: |- + Policy for pulling OCI objects. Possible values are: + Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. + Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. + IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. + Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. + type: string + reference: + description: |- + Required: Image or artifact reference to be used. + Behaves in the same way as pod.spec.containers[*].image. + Pull secrets will be assembled in the same way as for the container image by looking up node credentials, SA image pull secrets, and pod spec image pull secrets. + More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management to default or override + container images in workload controllers like Deployments and StatefulSets. + type: string + type: object + iscsi: + description: |- + iscsi represents an ISCSI Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://examples.k8s.io/volumes/iscsi/README.md + properties: + chapAuthDiscovery: + description: chapAuthDiscovery defines whether + support iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: chapAuthSession defines whether + support iSCSI Session CHAP authentication + type: boolean + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + type: string + initiatorName: + description: |- + initiatorName is the custom iSCSI Initiator Name. + If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface + : will be created for the connection. + type: string + iqn: + description: iqn is the target iSCSI Qualified + Name. + type: string + iscsiInterface: + default: default + description: |- + iscsiInterface is the interface Name that uses an iSCSI transport. + Defaults to 'default' (tcp). + type: string + lun: + description: lun represents iSCSI Target + Lun number. + format: int32 + type: integer + portals: + description: |- + portals is the iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + items: + type: string + type: array + x-kubernetes-list-type: atomic + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + type: boolean + secretRef: + description: secretRef is the CHAP Secret + for iSCSI target and initiator authentication + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + description: |- + targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + nfs: + description: |- + nfs represents an NFS mount on the host that shares a pod's lifetime + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + properties: + path: + description: |- + path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + readOnly: + description: |- + readOnly here will force the NFS export to be mounted with read-only permissions. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: boolean + server: + description: |- + server is the hostname or IP address of the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: |- + persistentVolumeClaimVolumeSource represents a reference to a + PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + properties: + claimName: + description: |- + claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + type: string + readOnly: + description: |- + readOnly Will force the ReadOnly setting in VolumeMounts. + Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: |- + photonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine. + Deprecated: PhotonPersistentDisk is deprecated and the in-tree photonPersistentDisk type is no longer supported. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + pdID: + description: pdID is the ID that identifies + Photon Controller persistent disk + type: string + required: + - pdID + type: object + portworxVolume: + description: |- + portworxVolume represents a portworx volume attached and mounted on kubelets host machine. + Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type + are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate + is on. + properties: + fsType: + description: |- + fSType represents the filesystem type to mount + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: volumeID uniquely identifies + a Portworx volume + type: string + required: + - volumeID + type: object + projected: + description: projected items for all in one + resources secrets, configmaps, and downward + API + properties: + defaultMode: + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + sources: + description: |- + sources is the list of volume projections. Each entry in this list + handles one source. + items: + description: |- + Projection that may be projected along with other supported volume types. + Exactly one of these fields must be set. + properties: + clusterTrustBundle: + description: |- + ClusterTrustBundle allows a pod to access the `.spec.trustBundle` field + of ClusterTrustBundle objects in an auto-updating file. + + Alpha, gated by the ClusterTrustBundleProjection feature gate. + + ClusterTrustBundle objects can either be selected by name, or by the + combination of signer name and a label selector. + + Kubelet performs aggressive normalization of the PEM contents written + into the pod filesystem. Esoteric PEM features such as inter-block + comments and block headers are stripped. Certificates are deduplicated. + The ordering of certificates within the file is arbitrary, and Kubelet + may change the order over time. + properties: + labelSelector: + description: |- + Select all ClusterTrustBundles that match this label selector. Only has + effect if signerName is set. Mutually-exclusive with name. If unset, + interpreted as "match nothing". If set but empty, interpreted as "match + everything". + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + name: + description: |- + Select a single ClusterTrustBundle by object name. Mutually-exclusive + with signerName and labelSelector. + type: string + optional: + description: |- + If true, don't block pod startup if the referenced ClusterTrustBundle(s) + aren't available. If using name, then the named ClusterTrustBundle is + allowed not to exist. If using signerName, then the combination of + signerName and labelSelector is allowed to match zero + ClusterTrustBundles. + type: boolean + path: + description: Relative path from + the volume root to write the + bundle. + type: string + signerName: + description: |- + Select all ClusterTrustBundles that match this signer name. + Mutually-exclusive with name. The contents of all selected + ClusterTrustBundles will be unified and deduplicated. + type: string + required: + - path + type: object + configMap: + description: configMap information + about the configMap data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key + to a path within a volume. + properties: + key: + description: key is the + key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional specify + whether the ConfigMap or its + keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + description: downwardAPI information + about the downwardAPI data to project + properties: + items: + description: Items is a list of + DownwardAPIVolume file + items: + description: DownwardAPIVolumeFile + represents information to + create the file containing + the pod field + properties: + fieldRef: + description: 'Required: + Selects a field of the + pod: only annotations, + labels, name, namespace + and uid are supported.' + properties: + apiVersion: + description: Version + of the schema the + FieldPath is written + in terms of, defaults + to "v1". + type: string + fieldPath: + description: Path of + the field to select + in the specified API + version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: + Path is the relative + path name of the file + to be created. Must not + be absolute or contain + the ''..'' path. Must + be utf-8 encoded. The + first item of the relative + path must not start with + ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container + name: required for + volumes, optional + for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies + the output format + of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: + resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + secret: + description: secret information about + the secret data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key + to a path within a volume. + properties: + key: + description: key is the + key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional field specify + whether the Secret or its key + must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + description: serviceAccountToken is + information about the serviceAccountToken + data to project + properties: + audience: + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. + type: string + expirationSeconds: + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. + format: int64 + type: integer + path: + description: |- + path is the path relative to the mount point of the file to project the + token into. + type: string + required: + - path + type: object + type: object + type: array + x-kubernetes-list-type: atomic + type: object + quobyte: + description: |- + quobyte represents a Quobyte mount on the host that shares a pod's lifetime. + Deprecated: Quobyte is deprecated and the in-tree quobyte type is no longer supported. + properties: + group: + description: |- + group to map volume access to + Default is no group + type: string + readOnly: + description: |- + readOnly here will force the Quobyte volume to be mounted with read-only permissions. + Defaults to false. + type: boolean + registry: + description: |- + registry represents a single or multiple Quobyte Registry services + specified as a string as host:port pair (multiple entries are separated with commas) + which acts as the central registry for volumes + type: string + tenant: + description: |- + tenant owning the given Quobyte volume in the Backend + Used with dynamically provisioned Quobyte volumes, value is set by the plugin + type: string + user: + description: |- + user to map volume access to + Defaults to serivceaccount user + type: string + volume: + description: volume is a string that references + an already created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: |- + rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. + Deprecated: RBD is deprecated and the in-tree rbd type is no longer supported. + More info: https://examples.k8s.io/volumes/rbd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + type: string + image: + description: |- + image is the rados image name. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + keyring: + default: /etc/ceph/keyring + description: |- + keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + monitors: + description: |- + monitors is a collection of Ceph monitors. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + items: + type: string + type: array + x-kubernetes-list-type: atomic + pool: + default: rbd + description: |- + pool is the rados pool name. + Default is rbd. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: boolean + secretRef: + description: |- + secretRef is name of the authentication secret for RBDUser. If provided + overrides keyring. + Default is nil. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + user: + default: admin + description: |- + user is the rados user name. + Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + required: + - image + - monitors + type: object + scaleIO: + description: |- + scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. + Deprecated: ScaleIO is deprecated and the in-tree scaleIO type is no longer supported. + properties: + fsType: + default: xfs + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". + Default is "xfs". + type: string + gateway: + description: gateway is the host address + of the ScaleIO API Gateway. + type: string + protectionDomain: + description: protectionDomain is the name + of the ScaleIO Protection Domain for the + configured storage. + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef references to the secret for ScaleIO user and other + sensitive information. If this is not provided, Login operation will fail. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + description: sslEnabled Flag enable/disable + SSL communication with Gateway, default + false + type: boolean + storageMode: + default: ThinProvisioned + description: |- + storageMode indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. + Default is ThinProvisioned. + type: string + storagePool: + description: storagePool is the ScaleIO + Storage Pool associated with the protection + domain. + type: string + system: + description: system is the name of the storage + system as configured in ScaleIO. + type: string + volumeName: + description: |- + volumeName is the name of a volume already created in the ScaleIO system + that is associated with this volume source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + properties: + defaultMode: + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + optional: + description: optional field specify whether + the Secret or its keys must be defined + type: boolean + secretName: + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: string + type: object + storageos: + description: |- + storageOS represents a StorageOS volume attached and mounted on Kubernetes nodes. + Deprecated: StorageOS is deprecated and the in-tree storageos type is no longer supported. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef specifies the secret to use for obtaining the StorageOS API + credentials. If not specified, default values will be attempted. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + description: |- + volumeName is the human-readable name of the StorageOS volume. Volume + names are only unique within a namespace. + type: string + volumeNamespace: + description: |- + volumeNamespace specifies the scope of the volume within StorageOS. If no + namespace is specified then the Pod's namespace will be used. This allows the + Kubernetes name scoping to be mirrored within StorageOS for tighter integration. + Set VolumeName to any name to override the default behaviour. + Set to "default" if you are not using namespaces within StorageOS. + Namespaces that do not pre-exist within StorageOS will be created. + type: string + type: object + vsphereVolume: + description: |- + vsphereVolume represents a vSphere volume attached and mounted on kubelets host machine. + Deprecated: VsphereVolume is deprecated. All operations for the in-tree vsphereVolume type + are redirected to the csi.vsphere.vmware.com CSI driver. + properties: + fsType: + description: |- + fsType is filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + storagePolicyID: + description: storagePolicyID is the storage + Policy Based Management (SPBM) profile + ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: storagePolicyName is the storage + Policy Based Management (SPBM) profile + name. + type: string + volumePath: + description: volumePath is the path that + identifies vSphere volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + type: object + strategy: + description: The daemonset strategy to use to replace + existing pods with new ones. + properties: + rollingUpdate: + description: Rolling update config params. Present + only if type = "RollingUpdate". + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of nodes with an existing available DaemonSet pod that + can have an updated DaemonSet pod during during an update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up to a minimum of 1. + Default value is 0. + Example: when this is set to 30%, at most 30% of the total number of nodes + that should be running the daemon pod (i.e. status.desiredNumberScheduled) + can have their a new pod created before the old pod is marked as deleted. + The update starts by launching new pods on 30% of nodes. Once an updated + pod is available (Ready for at least minReadySeconds) the old DaemonSet pod + on that node is marked deleted. If the old pod becomes unavailable for any + reason (Ready transitions to false, is evicted, or is drained) an updated + pod is immediatedly created on that node without considering surge limits. + Allowing surge implies the possibility that the resources consumed by the + daemonset on any given node can double if the readiness check fails, and + so resource intensive daemonsets should take into account that they may + cause evictions during disruption. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of DaemonSet pods that can be unavailable during the + update. Value can be an absolute number (ex: 5) or a percentage of total + number of DaemonSet pods at the start of the update (ex: 10%). Absolute + number is calculated from percentage by rounding up. + This cannot be 0 if MaxSurge is 0 + Default value is 1. + Example: when this is set to 30%, at most 30% of the total number of nodes + that should be running the daemon pod (i.e. status.desiredNumberScheduled) + can have their pods stopped for an update at any given time. The update + starts by stopping at most 30% of those DaemonSet pods and then brings + up new DaemonSet pods in their place. Once the new pods are available, + it then proceeds onto other DaemonSet pods, thus ensuring that at least + 70% of original number of DaemonSet pods are available at all times during + the update. + x-kubernetes-int-or-string: true + type: object + type: + description: Type of daemon set update. Can be "RollingUpdate" + or "OnDelete". Default is RollingUpdate. + type: string + type: object + type: object + envoyDeployment: + description: |- + EnvoyDeployment defines the desired state of the Envoy deployment resource. + If unspecified, default settings for the managed Envoy deployment resource + are applied. + properties: + container: + description: Container defines the desired specification + of main container. + properties: + env: + description: List of environment variables to set + in the container. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. + Must be a C_IDENTIFIER. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: |- + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + properties: + apiVersion: + description: Version of the schema the + FieldPath is written in terms of, + defaults to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + properties: + containerName: + description: 'Container name: required + for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults + to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to + select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in + the pod's namespace + properties: + key: + description: The key of the secret to + select from. Must be a valid secret + key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + image: + description: Image specifies the EnvoyProxy container + image to be used, instead of the default image. + type: string + resources: + description: |- + Resources required by this container. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry + in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: |- + 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/ + properties: + allowPrivilegeEscalation: + description: |- + AllowPrivilegeEscalation controls whether a process can gain more + privileges than its parent process. This bool directly controls if + the no_new_privs flag will be set on the container process. + AllowPrivilegeEscalation is true always when the container is: + 1) run as Privileged + 2) has CAP_SYS_ADMIN + Note that this field cannot be set when spec.os.name is windows. + type: boolean + appArmorProfile: + description: |- + appArmorProfile is the AppArmor options to use by this container. If set, this profile + overrides the pod's appArmorProfile. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile loaded on the node that should be used. + The profile must be preconfigured on the node to work. + Must match the loaded name of the profile. + Must be set if and only if type is "Localhost". + type: string + type: + description: |- + type indicates which kind of AppArmor profile will be applied. + Valid options are: + Localhost - a profile pre-loaded on the node. + RuntimeDefault - the container runtime's default profile. + Unconfined - no AppArmor enforcement. + type: string + required: + - type + type: object + capabilities: + description: |- + The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the container runtime. + Note that this field cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX + capabilities type + type: string + type: array + x-kubernetes-list-type: atomic + drop: + description: Removed capabilities + items: + description: Capability represent POSIX + capabilities type + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + description: |- + Run container in privileged mode. + Processes in privileged containers are essentially equivalent to root on the host. + Defaults to false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: |- + procMount denotes the type of proc mount to use for the containers. + The default value is Default which uses the container runtime defaults for + readonly paths and masked paths. + This requires the ProcMountType feature flag to be enabled. + Note that this field cannot be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: |- + Whether this container has a read-only root filesystem. + Default is false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label + that applies to the container. + type: string + role: + description: Role is a SELinux role label + that applies to the container. + type: string + type: + description: Type is a SELinux type label + that applies to the container. + type: string + user: + description: User is a SELinux user label + that applies to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by this container. If seccomp options are + provided at both the pod & container level, the container options + override the pod options. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + windowsOptions: + description: |- + The Windows specific settings applied to all containers. + If unspecified, the options from the PodSecurityContext will be used. + If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: |- + GMSACredentialSpec is where the GMSA admission webhook + (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the + GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the + name of the GMSA credential spec to use. + type: string + hostProcess: + description: |- + HostProcess determines if a container should be run as a 'Host Process' container. + All of a Pod's containers must have the same effective HostProcess value + (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). + In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: |- + The UserName in Windows to run the entrypoint of the container process. + Defaults to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + volumeMounts: + description: |- + VolumeMounts are volumes to mount into the container's filesystem. + Cannot be updated. + items: + description: VolumeMount describes a mounting of + a Volume within a container. + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + mountPropagation determines how mounts are propagated from the host + to container and the other way around. + When not set, MountPropagationNone is used. + This field is beta in 1.10. + When RecursiveReadOnly is set to IfPossible or to Enabled, MountPropagation must be None or unspecified + (which defaults to None). + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + recursiveReadOnly: + description: |- + RecursiveReadOnly specifies whether read-only mounts should be handled + recursively. + + If ReadOnly is false, this field has no meaning and must be unspecified. + + If ReadOnly is true, and this field is set to Disabled, the mount is not made + recursively read-only. If this field is set to IfPossible, the mount is made + recursively read-only, if it is supported by the container runtime. If this + field is set to Enabled, the mount is made recursively read-only if it is + supported by the container runtime, otherwise the pod will not be started and + an error will be generated to indicate the reason. + + If this field is set to IfPossible or Enabled, MountPropagation must be set to + None (or be unspecified, which defaults to None). + + If this field is not specified, it is treated as an equivalent of Disabled. + type: string + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: |- + Expanded path within the volume from which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + Defaults to "" (volume's root). + SubPathExpr and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + type: object + initContainers: + description: |- + List of initialization containers belonging to the pod. + More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ + items: + description: A single application container that you + want to run within a pod. + properties: + args: + description: |- + Arguments to the entrypoint. + The container image's CMD is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be updated. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + items: + type: string + type: array + x-kubernetes-list-type: atomic + command: + description: |- + Entrypoint array. Not executed within a shell. + The container image's ENTRYPOINT is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be updated. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + items: + type: string + type: array + x-kubernetes-list-type: atomic + env: + description: |- + List of environment variables to set in the container. + Cannot be updated. + items: + description: EnvVar represents an environment + variable present in a Container. + properties: + name: + description: Name of the environment variable. + Must be a C_IDENTIFIER. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: |- + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + properties: + apiVersion: + description: Version of the schema + the FieldPath is written in terms + of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to + select in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + properties: + containerName: + description: 'Container name: required + for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output + format of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to + select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret + in the pod's namespace + properties: + key: + description: The key of the secret + to select from. Must be a valid + secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + description: |- + List of sources to populate environment variables in the container. + The keys defined within a source must be a C_IDENTIFIER. All invalid keys + will be reported as an event when the container is starting. When a key exists in multiple + sources, the value associated with the last source will take precedence. + Values defined by an Env with a duplicate key will take precedence. + Cannot be updated. + items: + description: EnvFromSource represents the source + of a set of ConfigMaps or Secrets + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap + must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + description: Optional text to prepend to the + name of each environment variable. Must + be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret + must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + image: + description: |- + Container image name. + More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management to default or override + container images in workload controllers like Deployments and StatefulSets. + type: string + imagePullPolicy: + description: |- + Image pull policy. + One of Always, Never, IfNotPresent. + Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/containers/images#updating-images + type: string + lifecycle: + description: |- + Actions that the management system should take in response to container lifecycle events. + Cannot be updated. + properties: + postStart: + description: |- + PostStart is called immediately after a container is created. If the handler fails, + the container is terminated and restarted according to its restart policy. + Other management of the container blocks until the hook completes. + More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks + properties: + exec: + description: Exec specifies a command to + execute in the container. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + description: HTTPGet specifies an HTTP GET + request to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in + the request. HTTP allows repeated + headers. + items: + description: HTTPHeader describes + a custom header to be used in HTTP + probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field + value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + sleep: + description: Sleep represents a duration + that the container should sleep. + properties: + seconds: + description: Seconds is the number of + seconds to sleep. + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + description: |- + Deprecated. TCPSocket is NOT supported as a LifecycleHandler and kept + for backward compatibility. There is no validation of this field and + lifecycle hooks will fail at runtime when it is specified. + properties: + host: + description: 'Optional: Host name to + connect to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + description: |- + PreStop is called immediately before a container is terminated due to an + API request or management event such as liveness/startup probe failure, + preemption, resource contention, etc. The handler is not called if the + container crashes or exits. The Pod's termination grace period countdown begins before the + PreStop hook is executed. Regardless of the outcome of the handler, the + container will eventually terminate within the Pod's termination grace + period (unless delayed by finalizers). Other management of the container blocks until the hook completes + or until the termination grace period is reached. + More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks + properties: + exec: + description: Exec specifies a command to + execute in the container. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + description: HTTPGet specifies an HTTP GET + request to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in + the request. HTTP allows repeated + headers. + items: + description: HTTPHeader describes + a custom header to be used in HTTP + probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field + value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + sleep: + description: Sleep represents a duration + that the container should sleep. + properties: + seconds: + description: Seconds is the number of + seconds to sleep. + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + description: |- + Deprecated. TCPSocket is NOT supported as a LifecycleHandler and kept + for backward compatibility. There is no validation of this field and + lifecycle hooks will fail at runtime when it is specified. + properties: + host: + description: 'Optional: Host name to + connect to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + stopSignal: + description: |- + StopSignal defines which signal will be sent to a container when it is being stopped. + If not specified, the default is defined by the container runtime in use. + StopSignal can only be set for Pods with a non-empty .spec.os.name + type: string + type: object + livenessProbe: + description: |- + Periodic probe of container liveness. + Container will be restarted if the probe fails. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + properties: + exec: + description: Exec specifies a command to execute + in the container. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies a GRPC HealthCheckRequest. + properties: + port: + description: Port number of the gRPC service. + Number must be in the range 1 to 65535. + format: int32 + type: integer + service: + default: "" + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies an HTTP GET request + to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies a connection + to a TCP port. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object + name: + description: |- + Name of the container specified as a DNS_LABEL. + Each container in a pod must have a unique name (DNS_LABEL). + Cannot be updated. + type: string + ports: + description: |- + List of ports to expose from the container. Not specifying a port here + DOES NOT prevent that port from being exposed. Any port which is + listening on the default "0.0.0.0" address inside a container will be + accessible from the network. + Modifying this array with strategic merge patch may corrupt the data. + For more information See https://github.com/kubernetes/kubernetes/issues/108255. + Cannot be updated. + items: + description: ContainerPort represents a network + port in a single container. + properties: + containerPort: + description: |- + Number of port to expose on the pod's IP address. + This must be a valid port number, 0 < x < 65536. + format: int32 + type: integer + hostIP: + description: What host IP to bind the external + port to. + type: string + hostPort: + description: |- + Number of port to expose on the host. + If specified, this must be a valid port number, 0 < x < 65536. + If HostNetwork is specified, this must match ContainerPort. + Most containers do not need this. + format: int32 + type: integer + name: + description: |- + If specified, this must be an IANA_SVC_NAME and unique within the pod. Each + named port in a pod must have a unique name. Name for the port that can be + referred to by services. + type: string + protocol: + default: TCP + description: |- + Protocol for port. Must be UDP, TCP, or SCTP. + Defaults to "TCP". + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + description: |- + Periodic probe of container service readiness. + Container will be removed from service endpoints if the probe fails. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + properties: + exec: + description: Exec specifies a command to execute + in the container. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies a GRPC HealthCheckRequest. + properties: + port: + description: Port number of the gRPC service. + Number must be in the range 1 to 65535. + format: int32 + type: integer + service: + default: "" + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies an HTTP GET request + to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies a connection + to a TCP port. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object + resizePolicy: + description: Resources resize policy for the container. + items: + description: ContainerResizePolicy represents + resource resize policy for the container. + properties: + resourceName: + description: |- + Name of the resource to which this resource resize policy applies. + Supported values: cpu, memory. + type: string + restartPolicy: + description: |- + Restart policy to apply when specified resource is resized. + If not specified, it defaults to NotRequired. + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: + description: |- + Compute Resources required by this container. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one + entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + restartPolicy: + description: |- + RestartPolicy defines the restart behavior of individual containers in a pod. + This field may only be set for init containers, and the only allowed value is "Always". + For non-init containers or when this field is not specified, + the restart behavior is defined by the Pod's restart policy and the container type. + Setting the RestartPolicy as "Always" for the init container will have the following effect: + this init container will be continually restarted on + exit until all regular containers have terminated. Once all regular + containers have completed, all init containers with restartPolicy "Always" + will be shut down. This lifecycle differs from normal init containers and + is often referred to as a "sidecar" container. Although this init + container still starts in the init container sequence, it does not wait + for the container to complete before proceeding to the next init + container. Instead, the next init container starts immediately after this + init container is started, or after any startupProbe has successfully + completed. + type: string + securityContext: + description: |- + 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/ + properties: + allowPrivilegeEscalation: + description: |- + AllowPrivilegeEscalation controls whether a process can gain more + privileges than its parent process. This bool directly controls if + the no_new_privs flag will be set on the container process. + AllowPrivilegeEscalation is true always when the container is: + 1) run as Privileged + 2) has CAP_SYS_ADMIN + Note that this field cannot be set when spec.os.name is windows. + type: boolean + appArmorProfile: + description: |- + appArmorProfile is the AppArmor options to use by this container. If set, this profile + overrides the pod's appArmorProfile. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile loaded on the node that should be used. + The profile must be preconfigured on the node to work. + Must match the loaded name of the profile. + Must be set if and only if type is "Localhost". + type: string + type: + description: |- + type indicates which kind of AppArmor profile will be applied. + Valid options are: + Localhost - a profile pre-loaded on the node. + RuntimeDefault - the container runtime's default profile. + Unconfined - no AppArmor enforcement. + type: string + required: + - type + type: object + capabilities: + description: |- + The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the container runtime. + Note that this field cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX + capabilities type + type: string + type: array + x-kubernetes-list-type: atomic + drop: + description: Removed capabilities + items: + description: Capability represent POSIX + capabilities type + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + description: |- + Run container in privileged mode. + Processes in privileged containers are essentially equivalent to root on the host. + Defaults to false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: |- + procMount denotes the type of proc mount to use for the containers. + The default value is Default which uses the container runtime defaults for + readonly paths and masked paths. + This requires the ProcMountType feature flag to be enabled. + Note that this field cannot be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: |- + Whether this container has a read-only root filesystem. + Default is false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label + that applies to the container. + type: string + role: + description: Role is a SELinux role label + that applies to the container. + type: string + type: + description: Type is a SELinux type label + that applies to the container. + type: string + user: + description: User is a SELinux user label + that applies to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by this container. If seccomp options are + provided at both the pod & container level, the container options + override the pod options. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + windowsOptions: + description: |- + The Windows specific settings applied to all containers. + If unspecified, the options from the PodSecurityContext will be used. + If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: |- + GMSACredentialSpec is where the GMSA admission webhook + (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the + GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the + name of the GMSA credential spec to use. + type: string + hostProcess: + description: |- + HostProcess determines if a container should be run as a 'Host Process' container. + All of a Pod's containers must have the same effective HostProcess value + (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). + In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: |- + The UserName in Windows to run the entrypoint of the container process. + Defaults to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + startupProbe: + description: |- + StartupProbe indicates that the Pod has successfully initialized. + If specified, no other probes are executed until this completes successfully. + If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. + This can be used to provide different probe parameters at the beginning of a Pod's lifecycle, + when it might take a long time to load data or warm a cache, than during steady-state operation. + This cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + properties: + exec: + description: Exec specifies a command to execute + in the container. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies a GRPC HealthCheckRequest. + properties: + port: + description: Port number of the gRPC service. + Number must be in the range 1 to 65535. + format: int32 + type: integer + service: + default: "" + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies an HTTP GET request + to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies a connection + to a TCP port. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object + stdin: + description: |- + Whether this container should allocate a buffer for stdin in the container runtime. If this + is not set, reads from stdin in the container will always result in EOF. + Default is false. + type: boolean + stdinOnce: + description: |- + Whether the container runtime should close the stdin channel after it has been opened by + a single attach. When stdin is true the stdin stream will remain open across multiple attach + sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the + first client attaches to stdin, and then remains open and accepts data until the client disconnects, + at which time stdin is closed and remains closed until the container is restarted. If this + flag is false, a container processes that reads from stdin will never receive an EOF. + Default is false + type: boolean + terminationMessagePath: + description: |- + Optional: Path at which the file to which the container's termination message + will be written is mounted into the container's filesystem. + Message written is intended to be brief final status, such as an assertion failure message. + Will be truncated by the node if greater than 4096 bytes. The total message length across + all containers will be limited to 12kb. + Defaults to /dev/termination-log. + Cannot be updated. + type: string + terminationMessagePolicy: + description: |- + Indicate how the termination message should be populated. File will use the contents of + terminationMessagePath to populate the container status message on both success and failure. + FallbackToLogsOnError will use the last chunk of container log output if the termination + message file is empty and the container exited with an error. + The log output is limited to 2048 bytes or 80 lines, whichever is smaller. + Defaults to File. + Cannot be updated. + type: string + tty: + description: |- + Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. + Default is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block + devices to be used by the container. + items: + description: volumeDevice describes a mapping + of a raw block device within a container. + properties: + devicePath: + description: devicePath is the path inside + of the container that the device will be + mapped to. + type: string + name: + description: name must match the name of a + persistentVolumeClaim in the pod + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: + description: |- + Pod volumes to mount into the container's filesystem. + Cannot be updated. + items: + description: VolumeMount describes a mounting + of a Volume within a container. + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + mountPropagation determines how mounts are propagated from the host + to container and the other way around. + When not set, MountPropagationNone is used. + This field is beta in 1.10. + When RecursiveReadOnly is set to IfPossible or to Enabled, MountPropagation must be None or unspecified + (which defaults to None). + type: string + name: + description: This must match the Name of a + Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + recursiveReadOnly: + description: |- + RecursiveReadOnly specifies whether read-only mounts should be handled + recursively. + + If ReadOnly is false, this field has no meaning and must be unspecified. + + If ReadOnly is true, and this field is set to Disabled, the mount is not made + recursively read-only. If this field is set to IfPossible, the mount is made + recursively read-only, if it is supported by the container runtime. If this + field is set to Enabled, the mount is made recursively read-only if it is + supported by the container runtime, otherwise the pod will not be started and + an error will be generated to indicate the reason. + + If this field is set to IfPossible or Enabled, MountPropagation must be set to + None (or be unspecified, which defaults to None). + + If this field is not specified, it is treated as an equivalent of Disabled. + type: string + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: |- + Expanded path within the volume from which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + Defaults to "" (volume's root). + SubPathExpr and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + description: |- + Container's working directory. + If not specified, the container runtime's default will be used, which + might be configured in the container image. + Cannot be updated. + type: string + required: + - name + type: object + type: array + name: + description: |- + Name of the deployment. + When unset, this defaults to an autogenerated name. + type: string + patch: + description: Patch defines how to perform the patch operation + to deployment + properties: + type: + description: |- + Type is the type of merge operation to perform + + By default, StrategicMerge is used as the patch type. + type: string + value: + description: Object contains the raw configuration + for merged object + x-kubernetes-preserve-unknown-fields: true + required: + - value + type: object + pod: + description: Pod defines the desired specification of + pod. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + 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. + If it's null, this PodAffinityTerm matches with no 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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. + If it's null, this PodAffinityTerm matches with no 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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. + If it's null, this PodAffinityTerm matches with no 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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. + If it's null, this PodAffinityTerm matches with no 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + type: object + type: object + annotations: + additionalProperties: + type: string + description: |- + Annotations are the annotations that should be appended to the pods. + By default, no pod annotations are appended. + type: object + imagePullSecrets: + description: |- + ImagePullSecrets is an optional list of references to secrets + in the same namespace to use for pulling any of the images used by this PodSpec. + If specified, these secrets will be passed to individual puller implementations for them to use. + More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + labels: + additionalProperties: + type: string + description: |- + Labels are the additional labels that should be tagged to the pods. + By default, no additional pod labels are tagged. + type: object + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is a selector which must be true for the pod to fit on a node. + Selector which must match a node's labels for the pod to be scheduled on that node. + More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + type: object + securityContext: + description: |- + SecurityContext holds pod-level security attributes and common container settings. + Optional: Defaults to empty. See type description for default values of each field. + properties: + appArmorProfile: + description: |- + appArmorProfile is the AppArmor options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile loaded on the node that should be used. + The profile must be preconfigured on the node to work. + Must match the loaded name of the profile. + Must be set if and only if type is "Localhost". + type: string + type: + description: |- + type indicates which kind of AppArmor profile will be applied. + Valid options are: + Localhost - a profile pre-loaded on the node. + RuntimeDefault - the container runtime's default profile. + Unconfined - no AppArmor enforcement. + type: string + required: + - type + type: object + fsGroup: + description: |- + A special supplemental group that applies to all containers in a pod. + Some volume types allow the Kubelet to change the ownership of that volume + to be owned by the pod: + + 1. The owning GID will be the FSGroup + 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + 3. The permission bits are OR'd with rw-rw---- + + If unset, the Kubelet will not modify the ownership and permissions of any volume. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + fsGroupChangePolicy: + description: |- + fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + before being exposed inside Pod. This field will only apply to + volume types which support fsGroup based ownership(and permissions). + It will have no effect on ephemeral volume types such as: secret, configmaps + and emptydir. + Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + Note that this field cannot be set when spec.os.name is windows. + type: string + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxChangePolicy: + description: |- + seLinuxChangePolicy defines how the container's SELinux label is applied to all volumes used by the Pod. + It has no effect on nodes that do not support SELinux or to volumes does not support SELinux. + Valid values are "MountOption" and "Recursive". + + "Recursive" means relabeling of all files on all Pod volumes by the container runtime. + This may be slow for large volumes, but allows mixing privileged and unprivileged Pods sharing the same volume on the same node. + + "MountOption" mounts all eligible Pod volumes with `-o context` mount option. + This requires all Pods that share the same volume to use the same SELinux label. + It is not possible to share the same volume among privileged and unprivileged Pods. + Eligible volumes are in-tree FibreChannel and iSCSI volumes, and all CSI volumes + whose CSI driver announces SELinux support by setting spec.seLinuxMount: true in their + CSIDriver instance. Other volumes are always re-labelled recursively. + "MountOption" value is allowed only when SELinuxMount feature gate is enabled. + + If not specified and SELinuxMount feature gate is enabled, "MountOption" is used. + If not specified and SELinuxMount feature gate is disabled, "MountOption" is used for ReadWriteOncePod volumes + and "Recursive" for all other volumes. + + This field affects only Pods that have SELinux label set, either in PodSecurityContext or in SecurityContext of all containers. + + All Pods that use the same volume should use the same seLinuxChangePolicy, otherwise some pods can get stuck in ContainerCreating state. + Note that this field cannot be set when spec.os.name is windows. + type: string + seLinuxOptions: + description: |- + The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in SecurityContext. If set in + both SecurityContext and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label + that applies to the container. + type: string + role: + description: Role is a SELinux role label + that applies to the container. + type: string + type: + description: Type is a SELinux type label + that applies to the container. + type: string + user: + description: User is a SELinux user label + that applies to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + supplementalGroups: + description: |- + A list of groups applied to the first process run in each container, in + addition to the container's primary GID and fsGroup (if specified). If + the SupplementalGroupsPolicy feature is enabled, the + supplementalGroupsPolicy field determines whether these are in addition + to or instead of any group memberships defined in the container image. + If unspecified, no additional groups are added, though group memberships + defined in the container image may still be used, depending on the + supplementalGroupsPolicy field. + Note that this field cannot be set when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + description: |- + Defines how supplemental groups of the first container processes are calculated. + Valid values are "Merge" and "Strict". If not specified, "Merge" is used. + (Alpha) Using the field requires the SupplementalGroupsPolicy feature gate to be enabled + and the container runtime must implement support for this feature. + Note that this field cannot be set when spec.os.name is windows. + type: string + sysctls: + description: |- + Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + sysctls (by the container runtime) might fail to launch. + Note that this field cannot be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter + to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: + description: |- + The Windows specific settings applied to all containers. + If unspecified, the options within a container's SecurityContext will be used. + If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: |- + GMSACredentialSpec is where the GMSA admission webhook + (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the + GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the + name of the GMSA credential spec to use. + type: string + hostProcess: + description: |- + HostProcess determines if a container should be run as a 'Host Process' container. + All of a Pod's containers must have the same effective HostProcess value + (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). + In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: |- + The UserName in Windows to run the entrypoint of the container process. + Defaults to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + 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 + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: TopologySpreadConstraint specifies + how to spread matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + volumes: + description: |- + Volumes that can be mounted by containers belonging to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes + items: + description: Volume represents a named volume in + a pod that may be accessed by any container in + the pod. + properties: + awsElasticBlockStore: + description: |- + awsElasticBlockStore represents an AWS Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + Deprecated: AWSElasticBlockStore is deprecated. All operations for the in-tree + awsElasticBlockStore type are redirected to the ebs.csi.aws.com CSI driver. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + format: int32 + type: integer + readOnly: + description: |- + readOnly value true will force the readOnly setting in VolumeMounts. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: boolean + volumeID: + description: |- + volumeID is unique ID of the persistent disk resource in AWS (Amazon EBS volume). + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + required: + - volumeID + type: object + azureDisk: + description: |- + azureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. + Deprecated: AzureDisk is deprecated. All operations for the in-tree azureDisk type + are redirected to the disk.csi.azure.com CSI driver. + properties: + cachingMode: + description: 'cachingMode is the Host Caching + mode: None, Read Only, Read Write.' + type: string + diskName: + description: diskName is the Name of the + data disk in the blob storage + type: string + diskURI: + description: diskURI is the URI of data + disk in the blob storage + type: string + fsType: + default: ext4 + description: |- + fsType is Filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + kind: + description: 'kind expected values are Shared: + multiple blob disks per storage account Dedicated: + single blob disk per storage account Managed: + azure managed data disk (only in managed + availability set). defaults to shared' + type: string + readOnly: + default: false + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: |- + azureFile represents an Azure File Service mount on the host and bind mount to the pod. + Deprecated: AzureFile is deprecated. All operations for the in-tree azureFile type + are redirected to the file.csi.azure.com CSI driver. + properties: + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: secretName is the name of + secret that contains Azure Storage Account + Name and Key + type: string + shareName: + description: shareName is the azure share + Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: |- + cephFS represents a Ceph FS mount on the host that shares a pod's lifetime. + Deprecated: CephFS is deprecated and the in-tree cephfs type is no longer supported. + properties: + monitors: + description: |- + monitors is Required: Monitors is a collection of Ceph monitors + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + items: + type: string + type: array + x-kubernetes-list-type: atomic + path: + description: 'path is Optional: Used as + the mounted root, rather than the full + Ceph tree, default is /' + type: string + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: boolean + secretFile: + description: |- + secretFile is Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + secretRef: + description: |- + secretRef is Optional: SecretRef is reference to the authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is optional: User is the rados user name, default is admin + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + required: + - monitors + type: object + cinder: + description: |- + cinder represents a cinder volume attached and mounted on kubelets host machine. + Deprecated: Cinder is deprecated. All operations for the in-tree cinder type + are redirected to the cinder.csi.openstack.org CSI driver. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: boolean + secretRef: + description: |- + secretRef is optional: points to a secret object containing parameters used to connect + to OpenStack. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + description: |- + volumeID used to identify the volume in cinder. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + required: + - volumeID + type: object + configMap: + description: configMap represents a configMap + that should populate this volume + properties: + defaultMode: + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional specify whether the + ConfigMap or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + description: csi (Container Storage Interface) + represents ephemeral storage that is handled + by certain external CSI drivers. + properties: + driver: + description: |- + driver is the name of the CSI driver that handles this volume. + Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: |- + fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the associated CSI driver + which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: |- + nodePublishSecretRef is a reference to the secret object containing + sensitive information to pass to the CSI driver to complete the CSI + NodePublishVolume and NodeUnpublishVolume calls. + This field is optional, and may be empty if no secret is required. If the + secret object contains more than one secret, all secret references are passed. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + description: |- + readOnly specifies a read-only configuration for the volume. + Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: |- + volumeAttributes stores driver-specific properties that are passed to the CSI + driver. Consult your driver's documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: downwardAPI represents downward + API about the pod that should populate this + volume + properties: + defaultMode: + description: |- + Optional: mode bits to use on created files by default. Must be a + Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: Items is a list of downward + API volume file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects a + field of the pod: only annotations, + labels, name, namespace and uid + are supported.' + properties: + apiVersion: + description: Version of the schema + the FieldPath is written in + terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field + to select in the specified API + version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file to + be created. Must not be absolute + or contain the ''..'' path. Must + be utf-8 encoded. The first item + of the relative path must not start + with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: + required for volumes, optional + for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output + format of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + emptyDir: + description: |- + emptyDir represents a temporary directory that shares a pod's lifetime. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + properties: + medium: + description: |- + medium represents what type of storage medium should back this directory. + The default is "" which means to use the node's default medium. + Must be an empty string (default) or Memory. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: |- + sizeLimit is the total amount of local storage required for this EmptyDir volume. + The size limit is also applicable for memory medium. + The maximum usage on memory medium EmptyDir would be the minimum value between + the SizeLimit specified here and the sum of memory limits of all containers in a pod. + The default is nil which means that the limit is undefined. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + description: |- + ephemeral represents a volume that is handled by a cluster storage driver. + The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, + and deleted when the pod is removed. + + Use this if: + a) the volume is only needed while the pod runs, + b) features of normal volumes like restoring from snapshot or capacity + tracking are needed, + c) the storage driver is specified through a storage class, and + d) the storage driver supports dynamic volume provisioning through + a PersistentVolumeClaim (see EphemeralVolumeSource for more + information on the connection between this volume type + and PersistentVolumeClaim). + + Use PersistentVolumeClaim or one of the vendor-specific + APIs for volumes that persist for longer than the lifecycle + of an individual pod. + + Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to + be used that way - see the documentation of the driver for + more information. + + A pod can use both types of ephemeral volumes and + persistent volumes at the same time. + properties: + volumeClaimTemplate: + description: |- + Will be used to create a stand-alone PVC to provision the volume. + The pod in which this EphemeralVolumeSource is embedded will be the + owner of the PVC, i.e. the PVC will be deleted together with the + pod. The name of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` array + entry. Pod validation will reject the pod if the concatenated name + is not valid for a PVC (for example, too long). + + An existing PVC with that name that is not owned by the pod + will *not* be used for the pod to avoid using an unrelated + volume by mistake. Starting the pod is then blocked until + the unrelated PVC is removed. If such a pre-created PVC is + meant to be used by the pod, the PVC has to updated with an + owner reference to the pod once the pod exists. Normally + this should not be necessary, but it may be useful when + manually reconstructing a broken cluster. + + This field is read-only and no changes will be made by Kubernetes + to the PVC after it has been created. + + Required, must not be nil. + properties: + metadata: + description: |- + May contain labels and annotations that will be copied into the PVC + when creating it. No other fields are allowed and will be rejected during + validation. + type: object + spec: + description: |- + The specification for the PersistentVolumeClaim. The entire content is + copied unchanged into the PVC that gets created from this + template. The same fields as in a PersistentVolumeClaim + are also valid here. + properties: + accessModes: + description: |- + accessModes contains the desired access modes the volume should have. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 + items: + type: string + type: array + x-kubernetes-list-type: atomic + dataSource: + description: |- + dataSource field can be used to specify either: + * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) + If the provisioner or an external controller can support the specified data source, + it will create a new volume based on the contents of the specified data source. + When the AnyVolumeDataSource feature gate is enabled, dataSource contents will be copied to dataSourceRef, + and dataSourceRef contents will be copied to dataSource when dataSourceRef.namespace is not specified. + If the namespace is specified, then dataSourceRef will not be copied to dataSource. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type + of resource being referenced + type: string + name: + description: Name is the name + of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + description: |- + dataSourceRef specifies the object from which to populate the volume with data, if a non-empty + volume is desired. This may be any object from a non-empty API group (non + core object) or a PersistentVolumeClaim object. + When this field is specified, volume binding will only succeed if the type of + the specified object matches some installed volume populator or dynamic + provisioner. + This field will replace the functionality of the dataSource field and as such + if both fields are non-empty, they must have the same value. For backwards + compatibility, when namespace isn't specified in dataSourceRef, + both fields (dataSource and dataSourceRef) will be set to the same + value automatically if one of them is empty and the other is non-empty. + When namespace is specified in dataSourceRef, + dataSource isn't set to the same value and must be empty. + There are three important differences between dataSource and dataSourceRef: + * While dataSource only allows two specific types of objects, dataSourceRef + allows any non-core object, as well as PersistentVolumeClaim objects. + * While dataSource ignores disallowed values (dropping them), dataSourceRef + preserves all values, and generates an error if a disallowed value is + specified. + * While dataSource only allows local objects, dataSourceRef allows objects + in any namespaces. + (Beta) Using this field requires the AnyVolumeDataSource feature gate to be enabled. + (Alpha) Using the namespace field of dataSourceRef requires the CrossNamespaceVolumeDataSource feature gate to be enabled. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type + of resource being referenced + type: string + name: + description: Name is the name + of resource being referenced + type: string + namespace: + description: |- + Namespace is the namespace of resource being referenced + Note that when a namespace is specified, a gateway.networking.k8s.io/ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. + (Alpha) This field requires the CrossNamespaceVolumeDataSource feature gate to be enabled. + type: string + required: + - kind + - name + type: object + resources: + description: |- + resources represents the minimum resources the volume should have. + If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements + that are lower than previous value but must still be higher than capacity recorded in the + status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + selector: + description: selector is a label + query over volumes to consider + for binding. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + storageClassName: + description: |- + storageClassName is the name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 + type: string + volumeAttributesClassName: + description: |- + volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim. + If specified, the CSI driver will create or update the volume with the attributes defined + in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName, + it can be changed after the claim is created. An empty string value means that no VolumeAttributesClass + will be applied to the claim but it's not allowed to reset this field to empty string once it is set. + If unspecified and the PersistentVolumeClaim is unbound, the default VolumeAttributesClass + will be set by the persistentvolume controller if it exists. + If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be + set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource + exists. + More info: https://kubernetes.io/docs/concepts/storage/volume-attributes-classes/ + (Beta) Using this field requires the VolumeAttributesClass feature gate to be enabled (off by default). + type: string + volumeMode: + description: |- + volumeMode defines what type of volume is required by the claim. + Value of Filesystem is implied when not included in claim spec. + type: string + volumeName: + description: volumeName is the binding + reference to the PersistentVolume + backing this claim. + type: string + type: object + required: + - spec + type: object + type: object + fc: + description: fc represents a Fibre Channel resource + that is attached to a kubelet's host machine + and then exposed to the pod. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + lun: + description: 'lun is Optional: FC target + lun number' + format: int32 + type: integer + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + targetWWNs: + description: 'targetWWNs is Optional: FC + target worldwide names (WWNs)' + items: + type: string + type: array + x-kubernetes-list-type: atomic + wwids: + description: |- + wwids Optional: FC volume world wide identifiers (wwids) + Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + flexVolume: + description: |- + flexVolume represents a generic volume resource that is + provisioned/attached using an exec based plugin. + Deprecated: FlexVolume is deprecated. Consider using a CSIDriver instead. + properties: + driver: + description: driver is the name of the driver + to use for this volume. + type: string + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + type: string + options: + additionalProperties: + type: string + description: 'options is Optional: this + field holds extra command options if any.' + type: object + readOnly: + description: |- + readOnly is Optional: defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef is Optional: secretRef is reference to the secret object containing + sensitive information to pass to the plugin scripts. This may be + empty if no secret object is specified. If the secret object + contains more than one secret, all secrets are passed to the plugin + scripts. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + description: |- + flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running. + Deprecated: Flocker is deprecated and the in-tree flocker type is no longer supported. + properties: + datasetName: + description: |- + datasetName is Name of the dataset stored as metadata -> name on the dataset for Flocker + should be considered as deprecated + type: string + datasetUUID: + description: datasetUUID is the UUID of + the dataset. This is unique identifier + of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: |- + gcePersistentDisk represents a GCE Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + Deprecated: GCEPersistentDisk is deprecated. All operations for the in-tree + gcePersistentDisk type are redirected to the pd.csi.storage.gke.io CSI driver. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + properties: + fsType: + description: |- + fsType is filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + format: int32 + type: integer + pdName: + description: |- + pdName is unique name of the PD resource in GCE. Used to identify the disk in GCE. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: boolean + required: + - pdName + type: object + gitRepo: + description: |- + gitRepo represents a git repository at a particular revision. + Deprecated: GitRepo is deprecated. To provision a container with a git repo, mount an + EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir + into the Pod's container. + properties: + directory: + description: |- + directory is the target directory name. + Must not contain or start with '..'. If '.' is supplied, the volume directory will be the + git repository. Otherwise, if specified, the volume will contain the git repository in + the subdirectory with the given name. + type: string + repository: + description: repository is the URL + type: string + revision: + description: revision is the commit hash + for the specified revision. + type: string + required: + - repository + type: object + glusterfs: + description: |- + glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. + Deprecated: Glusterfs is deprecated and the in-tree glusterfs type is no longer supported. + More info: https://examples.k8s.io/volumes/glusterfs/README.md + properties: + endpoints: + description: |- + endpoints is the endpoint name that details Glusterfs topology. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + path: + description: |- + path is the Glusterfs volume path. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + readOnly: + description: |- + readOnly here will force the Glusterfs volume to be mounted with read-only permissions. + Defaults to false. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: |- + hostPath represents a pre-existing file or directory on the host + machine that is directly exposed to the container. This is generally + used for system agents or other privileged things that are allowed + to see the host machine. Most containers will NOT need this. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + properties: + path: + description: |- + path of the directory on the host. + If the path is a symlink, it will follow the link to the real path. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + type: + description: |- + type for HostPath Volume + Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + required: + - path + type: object + image: + description: |- + image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. + The volume is resolved at pod startup depending on which PullPolicy value is provided: + + - Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. + - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. + - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. + + The volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. + A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. + The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. + The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. + The volume will be mounted read-only (ro) and non-executable files (noexec). + Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. + The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type. + properties: + pullPolicy: + description: |- + Policy for pulling OCI objects. Possible values are: + Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. + Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. + IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. + Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. + type: string + reference: + description: |- + Required: Image or artifact reference to be used. + Behaves in the same way as pod.spec.containers[*].image. + Pull secrets will be assembled in the same way as for the container image by looking up node credentials, SA image pull secrets, and pod spec image pull secrets. + More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management to default or override + container images in workload controllers like Deployments and StatefulSets. + type: string + type: object + iscsi: + description: |- + iscsi represents an ISCSI Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://examples.k8s.io/volumes/iscsi/README.md + properties: + chapAuthDiscovery: + description: chapAuthDiscovery defines whether + support iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: chapAuthSession defines whether + support iSCSI Session CHAP authentication + type: boolean + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + type: string + initiatorName: + description: |- + initiatorName is the custom iSCSI Initiator Name. + If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface + : will be created for the connection. + type: string + iqn: + description: iqn is the target iSCSI Qualified + Name. + type: string + iscsiInterface: + default: default + description: |- + iscsiInterface is the interface Name that uses an iSCSI transport. + Defaults to 'default' (tcp). + type: string + lun: + description: lun represents iSCSI Target + Lun number. + format: int32 + type: integer + portals: + description: |- + portals is the iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + items: + type: string + type: array + x-kubernetes-list-type: atomic + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + type: boolean + secretRef: + description: secretRef is the CHAP Secret + for iSCSI target and initiator authentication + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + description: |- + targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + nfs: + description: |- + nfs represents an NFS mount on the host that shares a pod's lifetime + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + properties: + path: + description: |- + path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + readOnly: + description: |- + readOnly here will force the NFS export to be mounted with read-only permissions. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: boolean + server: + description: |- + server is the hostname or IP address of the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: |- + persistentVolumeClaimVolumeSource represents a reference to a + PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + properties: + claimName: + description: |- + claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + type: string + readOnly: + description: |- + readOnly Will force the ReadOnly setting in VolumeMounts. + Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: |- + photonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine. + Deprecated: PhotonPersistentDisk is deprecated and the in-tree photonPersistentDisk type is no longer supported. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + pdID: + description: pdID is the ID that identifies + Photon Controller persistent disk + type: string + required: + - pdID + type: object + portworxVolume: + description: |- + portworxVolume represents a portworx volume attached and mounted on kubelets host machine. + Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type + are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate + is on. + properties: + fsType: + description: |- + fSType represents the filesystem type to mount + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: volumeID uniquely identifies + a Portworx volume + type: string + required: + - volumeID + type: object + projected: + description: projected items for all in one + resources secrets, configmaps, and downward + API + properties: + defaultMode: + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + sources: + description: |- + sources is the list of volume projections. Each entry in this list + handles one source. + items: + description: |- + Projection that may be projected along with other supported volume types. + Exactly one of these fields must be set. + properties: + clusterTrustBundle: + description: |- + ClusterTrustBundle allows a pod to access the `.spec.trustBundle` field + of ClusterTrustBundle objects in an auto-updating file. + + Alpha, gated by the ClusterTrustBundleProjection feature gate. + + ClusterTrustBundle objects can either be selected by name, or by the + combination of signer name and a label selector. + + Kubelet performs aggressive normalization of the PEM contents written + into the pod filesystem. Esoteric PEM features such as inter-block + comments and block headers are stripped. Certificates are deduplicated. + The ordering of certificates within the file is arbitrary, and Kubelet + may change the order over time. + properties: + labelSelector: + description: |- + Select all ClusterTrustBundles that match this label selector. Only has + effect if signerName is set. Mutually-exclusive with name. If unset, + interpreted as "match nothing". If set but empty, interpreted as "match + everything". + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + name: + description: |- + Select a single ClusterTrustBundle by object name. Mutually-exclusive + with signerName and labelSelector. + type: string + optional: + description: |- + If true, don't block pod startup if the referenced ClusterTrustBundle(s) + aren't available. If using name, then the named ClusterTrustBundle is + allowed not to exist. If using signerName, then the combination of + signerName and labelSelector is allowed to match zero + ClusterTrustBundles. + type: boolean + path: + description: Relative path from + the volume root to write the + bundle. + type: string + signerName: + description: |- + Select all ClusterTrustBundles that match this signer name. + Mutually-exclusive with name. The contents of all selected + ClusterTrustBundles will be unified and deduplicated. + type: string + required: + - path + type: object + configMap: + description: configMap information + about the configMap data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key + to a path within a volume. + properties: + key: + description: key is the + key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional specify + whether the ConfigMap or its + keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + description: downwardAPI information + about the downwardAPI data to project + properties: + items: + description: Items is a list of + DownwardAPIVolume file + items: + description: DownwardAPIVolumeFile + represents information to + create the file containing + the pod field + properties: + fieldRef: + description: 'Required: + Selects a field of the + pod: only annotations, + labels, name, namespace + and uid are supported.' + properties: + apiVersion: + description: Version + of the schema the + FieldPath is written + in terms of, defaults + to "v1". + type: string + fieldPath: + description: Path of + the field to select + in the specified API + version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: + Path is the relative + path name of the file + to be created. Must not + be absolute or contain + the ''..'' path. Must + be utf-8 encoded. The + first item of the relative + path must not start with + ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container + name: required for + volumes, optional + for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies + the output format + of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: + resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + secret: + description: secret information about + the secret data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key + to a path within a volume. + properties: + key: + description: key is the + key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional field specify + whether the Secret or its key + must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + description: serviceAccountToken is + information about the serviceAccountToken + data to project + properties: + audience: + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. + type: string + expirationSeconds: + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. + format: int64 + type: integer + path: + description: |- + path is the path relative to the mount point of the file to project the + token into. + type: string + required: + - path + type: object + type: object + type: array + x-kubernetes-list-type: atomic + type: object + quobyte: + description: |- + quobyte represents a Quobyte mount on the host that shares a pod's lifetime. + Deprecated: Quobyte is deprecated and the in-tree quobyte type is no longer supported. + properties: + group: + description: |- + group to map volume access to + Default is no group + type: string + readOnly: + description: |- + readOnly here will force the Quobyte volume to be mounted with read-only permissions. + Defaults to false. + type: boolean + registry: + description: |- + registry represents a single or multiple Quobyte Registry services + specified as a string as host:port pair (multiple entries are separated with commas) + which acts as the central registry for volumes + type: string + tenant: + description: |- + tenant owning the given Quobyte volume in the Backend + Used with dynamically provisioned Quobyte volumes, value is set by the plugin + type: string + user: + description: |- + user to map volume access to + Defaults to serivceaccount user + type: string + volume: + description: volume is a string that references + an already created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: |- + rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. + Deprecated: RBD is deprecated and the in-tree rbd type is no longer supported. + More info: https://examples.k8s.io/volumes/rbd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + type: string + image: + description: |- + image is the rados image name. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + keyring: + default: /etc/ceph/keyring + description: |- + keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + monitors: + description: |- + monitors is a collection of Ceph monitors. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + items: + type: string + type: array + x-kubernetes-list-type: atomic + pool: + default: rbd + description: |- + pool is the rados pool name. + Default is rbd. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: boolean + secretRef: + description: |- + secretRef is name of the authentication secret for RBDUser. If provided + overrides keyring. + Default is nil. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + user: + default: admin + description: |- + user is the rados user name. + Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + required: + - image + - monitors + type: object + scaleIO: + description: |- + scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. + Deprecated: ScaleIO is deprecated and the in-tree scaleIO type is no longer supported. + properties: + fsType: + default: xfs + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". + Default is "xfs". + type: string + gateway: + description: gateway is the host address + of the ScaleIO API Gateway. + type: string + protectionDomain: + description: protectionDomain is the name + of the ScaleIO Protection Domain for the + configured storage. + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef references to the secret for ScaleIO user and other + sensitive information. If this is not provided, Login operation will fail. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + description: sslEnabled Flag enable/disable + SSL communication with Gateway, default + false + type: boolean + storageMode: + default: ThinProvisioned + description: |- + storageMode indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. + Default is ThinProvisioned. + type: string + storagePool: + description: storagePool is the ScaleIO + Storage Pool associated with the protection + domain. + type: string + system: + description: system is the name of the storage + system as configured in ScaleIO. + type: string + volumeName: + description: |- + volumeName is the name of a volume already created in the ScaleIO system + that is associated with this volume source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + properties: + defaultMode: + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + optional: + description: optional field specify whether + the Secret or its keys must be defined + type: boolean + secretName: + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: string + type: object + storageos: + description: |- + storageOS represents a StorageOS volume attached and mounted on Kubernetes nodes. + Deprecated: StorageOS is deprecated and the in-tree storageos type is no longer supported. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef specifies the secret to use for obtaining the StorageOS API + credentials. If not specified, default values will be attempted. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + description: |- + volumeName is the human-readable name of the StorageOS volume. Volume + names are only unique within a namespace. + type: string + volumeNamespace: + description: |- + volumeNamespace specifies the scope of the volume within StorageOS. If no + namespace is specified then the Pod's namespace will be used. This allows the + Kubernetes name scoping to be mirrored within StorageOS for tighter integration. + Set VolumeName to any name to override the default behaviour. + Set to "default" if you are not using namespaces within StorageOS. + Namespaces that do not pre-exist within StorageOS will be created. + type: string + type: object + vsphereVolume: + description: |- + vsphereVolume represents a vSphere volume attached and mounted on kubelets host machine. + Deprecated: VsphereVolume is deprecated. All operations for the in-tree vsphereVolume type + are redirected to the csi.vsphere.vmware.com CSI driver. + properties: + fsType: + description: |- + fsType is filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + storagePolicyID: + description: storagePolicyID is the storage + Policy Based Management (SPBM) profile + ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: storagePolicyName is the storage + Policy Based Management (SPBM) profile + name. + type: string + volumePath: + description: volumePath is the path that + identifies vSphere volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + type: object + replicas: + description: Replicas is the number of desired pods. Defaults + to 1. + format: int32 + type: integer + strategy: + description: The deployment strategy to use to replace + existing pods with new ones. + properties: + rollingUpdate: + description: |- + Rolling update config params. Present only if DeploymentStrategyType = + RollingUpdate. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: + description: Type of deployment. Can be "Recreate" + or "RollingUpdate". Default is RollingUpdate. + type: string + type: object + type: object + envoyHpa: + description: EnvoyHpa defines the Horizontal Pod Autoscaler + settings for Envoy Proxy Deployment. + properties: + behavior: + description: |- + behavior configures the scaling behavior of the target + in both Up and Down directions (scaleUp and scaleDown fields respectively). + If not set, the default HPAScalingRules for scale up and scale down are used. + See k8s.io.autoscaling.v2.HorizontalPodAutoScalerBehavior. + properties: + scaleDown: + description: |- + scaleDown is scaling policy for scaling Down. + If not set, the default value is to allow to scale down to minReplicas pods, with a + 300 second stabilization window (i.e., the highest recommendation for + the last 300sec is used). + properties: + policies: + description: |- + policies is a list of potential scaling polices which can be used during scaling. + If not set, use the default values: + - For scale up: allow doubling the number of pods, or an absolute change of 4 pods in a 15s window. + - For scale down: allow all pods to be removed in a 15s window. + items: + description: HPAScalingPolicy is a single policy + which must hold true for a specified past + interval. + properties: + periodSeconds: + description: |- + periodSeconds specifies the window of time for which the policy should hold true. + PeriodSeconds must be greater than zero and less than or equal to 1800 (30 min). + format: int32 + type: integer + type: + description: type is used to specify the + scaling policy. + type: string + value: + description: |- + value contains the amount of change which is permitted by the policy. + It must be greater than zero + format: int32 + type: integer + required: + - periodSeconds + - type + - value + type: object + type: array + x-kubernetes-list-type: atomic + selectPolicy: + description: |- + selectPolicy is used to specify which policy should be used. + If not set, the default value Max is used. + type: string + stabilizationWindowSeconds: + description: |- + stabilizationWindowSeconds is the number of seconds for which past recommendations should be + considered while scaling up or scaling down. + StabilizationWindowSeconds must be greater than or equal to zero and less than or equal to 3600 (one hour). + If not set, use the default values: + - For scale up: 0 (i.e. no stabilization is done). + - For scale down: 300 (i.e. the stabilization window is 300 seconds long). + format: int32 + type: integer + tolerance: + anyOf: + - type: integer + - type: string + description: |- + tolerance is the tolerance on the ratio between the current and desired + metric value under which no updates are made to the desired number of + replicas (e.g. 0.01 for 1%). Must be greater than or equal to zero. If not + set, the default cluster-wide tolerance is applied (by default 10%). + + For example, if autoscaling is configured with a memory consumption target of 100Mi, + and scale-down and scale-up tolerances of 5% and 1% respectively, scaling will be + triggered when the actual consumption falls below 95Mi or exceeds 101Mi. + + This is an alpha field and requires enabling the HPAConfigurableTolerance + feature gate. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + scaleUp: + description: |- + scaleUp is scaling policy for scaling Up. + If not set, the default value is the higher of: + * increase no more than 4 pods per 60 seconds + * double the number of pods per 60 seconds + No stabilization is used. + properties: + policies: + description: |- + policies is a list of potential scaling polices which can be used during scaling. + If not set, use the default values: + - For scale up: allow doubling the number of pods, or an absolute change of 4 pods in a 15s window. + - For scale down: allow all pods to be removed in a 15s window. + items: + description: HPAScalingPolicy is a single policy + which must hold true for a specified past + interval. + properties: + periodSeconds: + description: |- + periodSeconds specifies the window of time for which the policy should hold true. + PeriodSeconds must be greater than zero and less than or equal to 1800 (30 min). + format: int32 + type: integer + type: + description: type is used to specify the + scaling policy. + type: string + value: + description: |- + value contains the amount of change which is permitted by the policy. + It must be greater than zero + format: int32 + type: integer + required: + - periodSeconds + - type + - value + type: object + type: array + x-kubernetes-list-type: atomic + selectPolicy: + description: |- + selectPolicy is used to specify which policy should be used. + If not set, the default value Max is used. + type: string + stabilizationWindowSeconds: + description: |- + stabilizationWindowSeconds is the number of seconds for which past recommendations should be + considered while scaling up or scaling down. + StabilizationWindowSeconds must be greater than or equal to zero and less than or equal to 3600 (one hour). + If not set, use the default values: + - For scale up: 0 (i.e. no stabilization is done). + - For scale down: 300 (i.e. the stabilization window is 300 seconds long). + format: int32 + type: integer + tolerance: + anyOf: + - type: integer + - type: string + description: |- + tolerance is the tolerance on the ratio between the current and desired + metric value under which no updates are made to the desired number of + replicas (e.g. 0.01 for 1%). Must be greater than or equal to zero. If not + set, the default cluster-wide tolerance is applied (by default 10%). + + For example, if autoscaling is configured with a memory consumption target of 100Mi, + and scale-down and scale-up tolerances of 5% and 1% respectively, scaling will be + triggered when the actual consumption falls below 95Mi or exceeds 101Mi. + + This is an alpha field and requires enabling the HPAConfigurableTolerance + feature gate. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + maxReplicas: + description: |- + maxReplicas is the upper limit for the number of replicas to which the autoscaler can scale up. + It cannot be less that minReplicas. + format: int32 + type: integer + x-kubernetes-validations: + - message: maxReplicas must be greater than 0 + rule: self > 0 + metrics: + description: |- + metrics contains the specifications for which to use to calculate the + desired replica count (the maximum replica count across all metrics will + be used). + If left empty, it defaults to being based on CPU utilization with average on 80% usage. + items: + description: |- + MetricSpec specifies how to scale based on a single metric + (only `type` and one other matching field should be set at once). + properties: + containerResource: + description: |- + containerResource refers to a resource metric (such as those specified in + requests and limits) known to Kubernetes describing a single container in + each pod of the current scale target (e.g. CPU or memory). Such metrics are + built in to Kubernetes, and have special scaling options on top of those + available to normal per-pod metrics using the "pods" source. + properties: + container: + description: container is the name of the container + in the pods of the scaling target + type: string + name: + description: name is the name of the resource + in question. + type: string + target: + description: target specifies the target value + for the given metric + properties: + averageUtilization: + description: |- + averageUtilization is the target value of the average of the + resource metric across all relevant pods, represented as a percentage of + the requested value of the resource for the pods. + Currently only valid for Resource metric source type + format: int32 + type: integer + averageValue: + anyOf: + - type: integer + - type: string + description: |- + averageValue is the target value of the average of the + metric across all relevant pods (as a quantity) + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: + description: type represents whether the + metric type is Utilization, Value, or + AverageValue + type: string + value: + anyOf: + - type: integer + - type: string + description: value is the target value of + the metric (as a quantity). + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - type + type: object + required: + - container + - name + - target + type: object + external: + description: |- + external refers to a global metric that is not associated + with any Kubernetes object. It allows autoscaling based on information + coming from components running outside of cluster + (for example length of queue in cloud messaging service, or + QPS from loadbalancer running outside of cluster). + properties: + metric: + description: metric identifies the target metric + by name and selector + properties: + name: + description: name is the name of the given + metric + type: string + selector: + description: |- + selector is the string-encoded form of a standard kubernetes label selector for the given metric + When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping. + When unset, just the metricName will be used to gather metrics. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + required: + - name + type: object + target: + description: target specifies the target value + for the given metric + properties: + averageUtilization: + description: |- + averageUtilization is the target value of the average of the + resource metric across all relevant pods, represented as a percentage of + the requested value of the resource for the pods. + Currently only valid for Resource metric source type + format: int32 + type: integer + averageValue: + anyOf: + - type: integer + - type: string + description: |- + averageValue is the target value of the average of the + metric across all relevant pods (as a quantity) + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: + description: type represents whether the + metric type is Utilization, Value, or + AverageValue + type: string + value: + anyOf: + - type: integer + - type: string + description: value is the target value of + the metric (as a quantity). + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - type + type: object + required: + - metric + - target + type: object + object: + description: |- + object refers to a metric describing a single kubernetes object + (for example, hits-per-second on an Ingress object). + properties: + describedObject: + description: describedObject specifies the descriptions + of a object,such as kind,name apiVersion + properties: + apiVersion: + description: apiVersion is the API version + of the referent + type: string + kind: + description: 'kind is the kind of the referent; + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'name is the name of the referent; + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + required: + - kind + - name + type: object + metric: + description: metric identifies the target metric + by name and selector + properties: + name: + description: name is the name of the given + metric + type: string + selector: + description: |- + selector is the string-encoded form of a standard kubernetes label selector for the given metric + When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping. + When unset, just the metricName will be used to gather metrics. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + required: + - name + type: object + target: + description: target specifies the target value + for the given metric + properties: + averageUtilization: + description: |- + averageUtilization is the target value of the average of the + resource metric across all relevant pods, represented as a percentage of + the requested value of the resource for the pods. + Currently only valid for Resource metric source type + format: int32 + type: integer + averageValue: + anyOf: + - type: integer + - type: string + description: |- + averageValue is the target value of the average of the + metric across all relevant pods (as a quantity) + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: + description: type represents whether the + metric type is Utilization, Value, or + AverageValue + type: string + value: + anyOf: + - type: integer + - type: string + description: value is the target value of + the metric (as a quantity). + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - type + type: object + required: + - describedObject + - metric + - target + type: object + pods: + description: |- + pods refers to a metric describing each pod in the current scale target + (for example, transactions-processed-per-second). The values will be + averaged together before being compared to the target value. + properties: + metric: + description: metric identifies the target metric + by name and selector + properties: + name: + description: name is the name of the given + metric + type: string + selector: + description: |- + selector is the string-encoded form of a standard kubernetes label selector for the given metric + When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping. + When unset, just the metricName will be used to gather metrics. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + required: + - name + type: object + target: + description: target specifies the target value + for the given metric + properties: + averageUtilization: + description: |- + averageUtilization is the target value of the average of the + resource metric across all relevant pods, represented as a percentage of + the requested value of the resource for the pods. + Currently only valid for Resource metric source type + format: int32 + type: integer + averageValue: + anyOf: + - type: integer + - type: string + description: |- + averageValue is the target value of the average of the + metric across all relevant pods (as a quantity) + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: + description: type represents whether the + metric type is Utilization, Value, or + AverageValue + type: string + value: + anyOf: + - type: integer + - type: string + description: value is the target value of + the metric (as a quantity). + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - type + type: object + required: + - metric + - target + type: object + resource: + description: |- + resource refers to a resource metric (such as those specified in + requests and limits) known to Kubernetes describing each pod in the + current scale target (e.g. CPU or memory). Such metrics are built in to + Kubernetes, and have special scaling options on top of those available + to normal per-pod metrics using the "pods" source. + properties: + name: + description: name is the name of the resource + in question. + type: string + target: + description: target specifies the target value + for the given metric + properties: + averageUtilization: + description: |- + averageUtilization is the target value of the average of the + resource metric across all relevant pods, represented as a percentage of + the requested value of the resource for the pods. + Currently only valid for Resource metric source type + format: int32 + type: integer + averageValue: + anyOf: + - type: integer + - type: string + description: |- + averageValue is the target value of the average of the + metric across all relevant pods (as a quantity) + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: + description: type represents whether the + metric type is Utilization, Value, or + AverageValue + type: string + value: + anyOf: + - type: integer + - type: string + description: value is the target value of + the metric (as a quantity). + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - type + type: object + required: + - name + - target + type: object + type: + description: |- + type is the type of metric source. It should be one of "ContainerResource", "External", + "Object", "Pods" or "Resource", each mapping to a matching field in the object. + type: string + required: + - type + type: object + type: array + minReplicas: + description: |- + minReplicas is the lower limit for the number of replicas to which the autoscaler + can scale down. It defaults to 1 replica. + format: int32 + type: integer + x-kubernetes-validations: + - message: minReplicas must be greater than 0 + rule: self > 0 + patch: + description: Patch defines how to perform the patch operation + to the HorizontalPodAutoscaler + properties: + type: + description: |- + Type is the type of merge operation to perform + + By default, StrategicMerge is used as the patch type. + type: string + value: + description: Object contains the raw configuration + for merged object + x-kubernetes-preserve-unknown-fields: true + required: + - value + type: object + required: + - maxReplicas + type: object + x-kubernetes-validations: + - message: maxReplicas cannot be less than minReplicas + rule: '!has(self.minReplicas) || self.maxReplicas >= self.minReplicas' + envoyPDB: + description: EnvoyPDB allows to control the pod disruption + budget of an Envoy Proxy. + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + MaxUnavailable specifies the maximum amount of pods (can be expressed as integers or as a percentage) that can be unavailable at all times during voluntary disruptions, + such as node drains or updates. This setting ensures that your envoy proxy maintains a certain level of availability + and resilience during maintenance operations. Cannot be combined with minAvailable. + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + description: |- + MinAvailable specifies the minimum amount of pods (can be expressed as integers or as a percentage) that must be available at all times during voluntary disruptions, + such as node drains or updates. This setting ensures that your envoy proxy maintains a certain level of availability + and resilience during maintenance operations. Cannot be combined with maxUnavailable. + x-kubernetes-int-or-string: true + patch: + description: Patch defines how to perform the patch operation + to the PodDisruptionBudget + properties: + type: + description: |- + Type is the type of merge operation to perform + + By default, StrategicMerge is used as the patch type. + type: string + value: + description: Object contains the raw configuration + for merged object + x-kubernetes-preserve-unknown-fields: true + required: + - value + type: object + type: object + x-kubernetes-validations: + - message: only one of minAvailable or maxUnavailable can + be specified + rule: (has(self.minAvailable) && !has(self.maxUnavailable)) + || (!has(self.minAvailable) && has(self.maxUnavailable)) + envoyService: + description: |- + EnvoyService defines the desired state of the Envoy service resource. + If unspecified, default settings for the managed Envoy service resource + are applied. + properties: + allocateLoadBalancerNodePorts: + description: |- + AllocateLoadBalancerNodePorts defines if NodePorts will be automatically allocated for + services with type LoadBalancer. Default is "true". It may be set to "false" if the cluster + load-balancer does not rely on NodePorts. If the caller requests specific NodePorts (by specifying a + value), those requests will be respected, regardless of this field. This field may only be set for + services with type LoadBalancer and will be cleared if the type is changed to any other type. + type: boolean + annotations: + additionalProperties: + type: string + description: |- + Annotations that should be appended to the service. + By default, no annotations are appended. + type: object + externalTrafficPolicy: + default: Local + description: |- + ExternalTrafficPolicy determines the externalTrafficPolicy for the Envoy Service. Valid options + are Local and Cluster. Default is "Local". "Local" means traffic will only go to pods on the node + receiving the traffic. "Cluster" means connections are loadbalanced to all pods in the cluster. + enum: + - Local + - Cluster + type: string + labels: + additionalProperties: + type: string + description: |- + Labels that should be appended to the service. + By default, no labels are appended. + type: object + loadBalancerClass: + description: |- + LoadBalancerClass, when specified, allows for choosing the LoadBalancer provider + implementation if more than one are available or is otherwise expected to be specified + type: string + loadBalancerIP: + description: |- + LoadBalancerIP defines the IP Address of the underlying load balancer service. This field + may be ignored if the load balancer provider does not support this feature. + This field has been deprecated in Kubernetes, but it is still used for setting the IP Address in some cloud + providers such as GCP. + type: string + x-kubernetes-validations: + - message: loadBalancerIP must be a valid IPv4 address + rule: self.matches(r"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$") + loadBalancerSourceRanges: + description: |- + LoadBalancerSourceRanges defines a list of allowed IP addresses which will be configured as + firewall rules on the platform providers load balancer. This is not guaranteed to be working as + it happens outside of kubernetes and has to be supported and handled by the platform provider. + This field may only be set for services with type LoadBalancer and will be cleared if the type + is changed to any other type. + items: + type: string + type: array + name: + description: |- + Name of the service. + When unset, this defaults to an autogenerated name. + type: string + patch: + description: Patch defines how to perform the patch operation + to the service + properties: + type: + description: |- + Type is the type of merge operation to perform + + By default, StrategicMerge is used as the patch type. + type: string + value: + description: Object contains the raw configuration + for merged object + x-kubernetes-preserve-unknown-fields: true + required: + - value + type: object + type: + default: LoadBalancer + description: |- + Type determines how the Service is exposed. Defaults to LoadBalancer. + Valid options are ClusterIP, LoadBalancer and NodePort. + "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. + "NodePort" means a service will be exposed on a static Port on all Nodes of the cluster. + enum: + - ClusterIP + - LoadBalancer + - NodePort + type: string + type: object + x-kubernetes-validations: + - message: allocateLoadBalancerNodePorts can only be set for + LoadBalancer type + rule: '!has(self.allocateLoadBalancerNodePorts) || self.type + == ''LoadBalancer''' + - message: loadBalancerSourceRanges can only be set for LoadBalancer + type + rule: '!has(self.loadBalancerSourceRanges) || self.type + == ''LoadBalancer''' + - message: loadBalancerIP can only be set for LoadBalancer + type + rule: '!has(self.loadBalancerIP) || self.type == ''LoadBalancer''' + useListenerPortAsContainerPort: + description: |- + UseListenerPortAsContainerPort disables the port shifting feature in the Envoy Proxy. + When set to false (default value), if the service port is a privileged port (1-1023), add a constant to the value converting it into an ephemeral port. + This allows the container to bind to the port without needing a CAP_NET_BIND_SERVICE capability. + type: boolean + type: object + x-kubernetes-validations: + - message: only one of envoyDeployment or envoyDaemonSet can be + specified + rule: ((has(self.envoyDeployment) && !has(self.envoyDaemonSet)) + || (!has(self.envoyDeployment) && has(self.envoyDaemonSet))) + || (!has(self.envoyDeployment) && !has(self.envoyDaemonSet)) + - message: cannot use envoyHpa if envoyDaemonSet is used + rule: ((has(self.envoyHpa) && !has(self.envoyDaemonSet)) || + (!has(self.envoyHpa) && has(self.envoyDaemonSet))) || (!has(self.envoyHpa) + && !has(self.envoyDaemonSet)) + type: + description: |- + 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". + enum: + - Kubernetes + - Custom + type: string + required: + - type + type: object + routingType: + description: |- + RoutingType can be set to "Service" to use the Service Cluster IP for routing to the backend, + or it can be set to "Endpoint" to use Endpoint routing. The default is "Endpoint". + type: string + shutdown: + description: Shutdown defines configuration for graceful envoy shutdown + process. + properties: + drainTimeout: + description: |- + DrainTimeout defines the graceful drain timeout. This should be less than the pod's terminationGracePeriodSeconds. + If unspecified, defaults to 60 seconds. + type: string + minDrainDuration: + description: |- + MinDrainDuration defines the minimum drain duration allowing time for endpoint deprogramming to complete. + If unspecified, defaults to 10 seconds. + type: string + type: object + telemetry: + description: Telemetry defines telemetry parameters for managed proxies. + properties: + accessLog: + description: |- + AccessLogs defines accesslog parameters for managed proxies. + If unspecified, will send default format to stdout. + properties: + disable: + description: Disable disables access logging for managed proxies + if set to true. + type: boolean + settings: + description: |- + Settings defines accesslog settings for managed proxies. + If unspecified, will send default format to stdout. + items: + properties: + format: + description: |- + Format defines the format of accesslog. + This will be ignored if sink type is ALS. + properties: + json: + additionalProperties: + type: string + description: |- + JSON is additional attributes that describe the specific event occurrence. + Structured format for the envoy access logs. Envoy [command operators](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#command-operators) + can be used as values for fields within the Struct. + It's required when the format type is "JSON". + type: object + text: + description: |- + Text defines the text accesslog format, following Envoy accesslog formatting, + It's required when the format type is "Text". + Envoy [command operators](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#command-operators) may be used in the format. + The [format string documentation](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#config-access-log-format-strings) provides more information. + type: string + type: + description: Type defines the type of accesslog + format. + enum: + - Text + - JSON + type: string + type: object + x-kubernetes-validations: + - message: If AccessLogFormat type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) : !has(self.text)' + - message: If AccessLogFormat type is JSON, json field + needs to be set. + rule: 'self.type == ''JSON'' ? has(self.json) : !has(self.json)' + matches: + description: |- + Matches defines the match conditions for accesslog in CEL expression. + An accesslog will be emitted only when one or more match conditions are evaluated to true. + Invalid [CEL](https://www.envoyproxy.io/docs/envoy/latest/xds/type/v3/cel.proto.html#common-expression-language-cel-proto) expressions will be ignored. + items: + type: string + maxItems: 10 + type: array + sinks: + description: Sinks defines the sinks of accesslog. + items: + description: ProxyAccessLogSink defines the sink of + accesslog. + properties: + als: + description: ALS defines the gRPC Access Log Service + (ALS) sink. + properties: + backendRef: + description: |- + BackendRef references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + + Deprecated: Use BackendRefs instead. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + backendRefs: + description: |- + BackendRefs references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + items: + description: BackendRef defines how an ObjectReference + that is specific to BackendRef. + properties: + fallback: + description: |- + Fallback indicates whether the backend is designated as a fallback. + Multiple fallback backends can be configured. + It is highly recommended to configure active or passive health checks to ensure that failover can be detected + when the active backends become unhealthy and to automatically readjust once the primary backends are healthy again. + The overprovisioning factor is set to 1.4, meaning the fallback backends will only start receiving traffic when + the health of the active backends falls below 72%. + type: boolean + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the + referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + maxItems: 16 + type: array + backendSettings: + description: |- + BackendSettings holds configuration for managing the connection + to the backend. + properties: + circuitBreaker: + description: |- + Circuit Breaker settings for the upstream connections and requests. + If not set, circuit breakers will be enabled with the default thresholds + properties: + maxConnections: + default: 1024 + description: The maximum number of + connections that Envoy will establish + to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRequests: + default: 1024 + description: The maximum number of + parallel requests that Envoy will + make to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRetries: + default: 1024 + description: The maximum number of + parallel retries that Envoy will + make to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxPendingRequests: + default: 1024 + description: The maximum number of + pending requests that Envoy will + queue to the referenced backend + defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxRequestsPerConnection: + description: |- + The maximum number of requests that Envoy will make over a single connection to the referenced backend defined within a xRoute rule. + Default: unlimited. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + perEndpoint: + description: PerEndpoint defines Circuit + Breakers that will apply per-endpoint + for an upstream cluster + properties: + maxConnections: + default: 1024 + description: MaxConnections configures + the maximum number of connections + that Envoy will establish per-endpoint + to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + type: object + type: object + connection: + description: Connection includes backend + connection settings. + properties: + bufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + BufferLimit Soft limit on size of the cluster’s connections read and write buffers. + BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space. + If unspecified, an implementation defined default is applied (32768 bytes). + For example, 20Mi, 1Gi, 256Ki etc. + Note: that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + socketBufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + SocketBufferLimit provides configuration for the maximum buffer size in bytes for each socket + to backend. + SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space. + For example, 20Mi, 1Gi, 256Ki etc. + Note that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + type: object + dns: + description: DNS includes dns resolution + settings. + properties: + dnsRefreshRate: + description: |- + DNSRefreshRate specifies the rate at which DNS records should be refreshed. + Defaults to 30 seconds. + type: string + lookupFamily: + description: |- + LookupFamily determines how Envoy would resolve DNS for Routes where the backend is specified as a fully qualified domain name (FQDN). + If set, this configuration overrides other defaults. + enum: + - IPv4 + - IPv6 + - IPv4Preferred + - IPv6Preferred + - IPv4AndIPv6 + type: string + respectDnsTtl: + description: |- + RespectDNSTTL indicates whether the DNS Time-To-Live (TTL) should be respected. + If the value is set to true, the DNS refresh rate will be set to the resource record’s TTL. + Defaults to true. + type: boolean + type: object + healthCheck: + description: HealthCheck allows gateway + to perform active health checking on + backends. + properties: + active: + description: Active health check configuration + properties: + grpc: + description: |- + GRPC defines the configuration of the GRPC health checker. + It's optional, and can only be used if the specified type is GRPC. + properties: + service: + description: |- + Service to send in the health check request. + If this is not specified, then the health check request applies to the entire + server and not to a specific service. + type: string + type: object + healthyThreshold: + default: 1 + description: HealthyThreshold + defines the number of healthy + health checks required before + a backend host is marked healthy. + format: int32 + minimum: 1 + type: integer + http: + description: |- + HTTP defines the configuration of http health checker. + It's required while the health checker type is HTTP. + properties: + expectedResponse: + description: ExpectedResponse + defines a list of HTTP expected + responses to match. + properties: + binary: + description: Binary payload + base64 encoded. + format: byte + type: string + text: + description: Text payload + in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines + the type of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type + is Text, text field needs + to be set. + rule: 'self.type == ''Text'' + ? has(self.text) : !has(self.text)' + - message: If payload type + is Binary, binary field + needs to be set. + rule: 'self.type == ''Binary'' + ? has(self.binary) : !has(self.binary)' + expectedStatuses: + description: |- + ExpectedStatuses defines a list of HTTP response statuses considered healthy. + Defaults to 200 only + items: + description: HTTPStatus + defines the http status + code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + method: + description: |- + Method defines the HTTP method used for health checking. + Defaults to GET + type: string + path: + description: Path defines + the HTTP path that will + be requested during health + checking. + maxLength: 1024 + minLength: 1 + type: string + required: + - path + type: object + interval: + default: 3s + description: Interval defines + the time between active health + checks. + format: duration + type: string + tcp: + description: |- + TCP defines the configuration of tcp health checker. + It's required while the health checker type is TCP. + properties: + receive: + description: Receive defines + the expected response payload. + properties: + binary: + description: Binary payload + base64 encoded. + format: byte + type: string + text: + description: Text payload + in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines + the type of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type + is Text, text field needs + to be set. + rule: 'self.type == ''Text'' + ? has(self.text) : !has(self.text)' + - message: If payload type + is Binary, binary field + needs to be set. + rule: 'self.type == ''Binary'' + ? has(self.binary) : !has(self.binary)' + send: + description: Send defines + the request payload. + properties: + binary: + description: Binary payload + base64 encoded. + format: byte + type: string + text: + description: Text payload + in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines + the type of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type + is Text, text field needs + to be set. + rule: 'self.type == ''Text'' + ? has(self.text) : !has(self.text)' + - message: If payload type + is Binary, binary field + needs to be set. + rule: 'self.type == ''Binary'' + ? has(self.binary) : !has(self.binary)' + type: object + timeout: + default: 1s + description: Timeout defines the + time to wait for a health check + response. + format: duration + type: string + type: + allOf: + - enum: + - HTTP + - TCP + - GRPC + - enum: + - HTTP + - TCP + - GRPC + description: Type defines the + type of health checker. + type: string + unhealthyThreshold: + default: 3 + description: UnhealthyThreshold + defines the number of unhealthy + health checks required before + a backend host is marked unhealthy. + format: int32 + minimum: 1 + type: integer + required: + - type + type: object + x-kubernetes-validations: + - message: If Health Checker type + is HTTP, http field needs to be + set. + rule: 'self.type == ''HTTP'' ? has(self.http) + : !has(self.http)' + - message: If Health Checker type + is TCP, tcp field needs to be + set. + rule: 'self.type == ''TCP'' ? has(self.tcp) + : !has(self.tcp)' + - message: The grpc field can only + be set if the Health Checker type + is GRPC. + rule: 'has(self.grpc) ? self.type + == ''GRPC'' : true' + panicThreshold: + description: |- + When number of unhealthy endpoints for a backend reaches this threshold + Envoy will disregard health status and balance across all endpoints. + It's designed to prevent a situation in which host failures cascade throughout the cluster + as load increases. If not set, the default value is 50%. To disable panic mode, set value to `0`. + format: int32 + maximum: 100 + minimum: 0 + type: integer + passive: + description: Passive passive check + configuration + properties: + baseEjectionTime: + default: 30s + description: BaseEjectionTime + defines the base duration for + which a host will be ejected + on consecutive failures. + format: duration + type: string + consecutive5XxErrors: + default: 5 + description: Consecutive5xxErrors + sets the number of consecutive + 5xx errors triggering ejection. + format: int32 + type: integer + consecutiveGatewayErrors: + default: 0 + description: ConsecutiveGatewayErrors + sets the number of consecutive + gateway errors triggering ejection. + format: int32 + type: integer + consecutiveLocalOriginFailures: + default: 5 + description: |- + ConsecutiveLocalOriginFailures sets the number of consecutive local origin failures triggering ejection. + Parameter takes effect only when split_external_local_origin_errors is set to true. + format: int32 + type: integer + interval: + default: 3s + description: Interval defines + the time between passive health + checks. + format: duration + type: string + maxEjectionPercent: + default: 10 + description: MaxEjectionPercent + sets the maximum percentage + of hosts in a cluster that can + be ejected. + format: int32 + type: integer + splitExternalLocalOriginErrors: + default: false + description: SplitExternalLocalOriginErrors + enables splitting of errors + between external and local origin. + type: boolean + type: object + type: object + http2: + description: HTTP2 provides HTTP/2 configuration + for backend connections. + properties: + initialConnectionWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialConnectionWindowSize sets the initial window size for HTTP/2 connections. + If not set, the default value is 1 MiB. + x-kubernetes-int-or-string: true + initialStreamWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialStreamWindowSize sets the initial window size for HTTP/2 streams. + If not set, the default value is 64 KiB(64*1024). + x-kubernetes-int-or-string: true + maxConcurrentStreams: + description: |- + MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection. + If not set, the default value is 100. + format: int32 + maximum: 2147483647 + minimum: 1 + type: integer + onInvalidMessage: + description: |- + OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error + It's recommended for L2 Envoy deployments to set this value to TerminateStream. + https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two + Default: TerminateConnection + type: string + type: object + loadBalancer: + description: |- + LoadBalancer policy to apply when routing traffic from the gateway to + the backend endpoints. Defaults to `LeastRequest`. + properties: + consistentHash: + description: |- + ConsistentHash defines the configuration when the load balancer type is + set to ConsistentHash + properties: + cookie: + description: Cookie configures + the cookie hash policy when + the consistent hash type is + set to Cookie. + properties: + attributes: + additionalProperties: + type: string + description: Additional Attributes + to set for the generated + cookie. + type: object + name: + description: |- + Name of the cookie to hash. + If this cookie does not exist in the request, Envoy will generate a cookie and set + the TTL on the response back to the client based on Layer 4 + attributes of the backend endpoint, to ensure that these future requests + go to the same backend endpoint. Make sure to set the TTL field for this case. + type: string + ttl: + description: |- + TTL of the generated cookie if the cookie is not present. This value sets the + Max-Age attribute value. + type: string + required: + - name + type: object + header: + description: Header configures + the header hash policy when + the consistent hash type is + set to Header. + properties: + name: + description: Name of the header + to hash. + type: string + required: + - name + type: object + tableSize: + default: 65537 + description: The table size for + consistent hashing, must be + prime number limited to 5000011. + format: int64 + maximum: 5000011 + minimum: 2 + type: integer + type: + description: |- + ConsistentHashType defines the type of input to hash on. Valid Type values are + "SourceIP", + "Header", + "Cookie". + enum: + - SourceIP + - Header + - Cookie + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If consistent hash type + is header, the header field must + be set. + rule: 'self.type == ''Header'' ? + has(self.header) : !has(self.header)' + - message: If consistent hash type + is cookie, the cookie field must + be set. + rule: 'self.type == ''Cookie'' ? + has(self.cookie) : !has(self.cookie)' + slowStart: + description: |- + SlowStart defines the configuration related to the slow start load balancer policy. + If set, during slow start window, traffic sent to the newly added hosts will gradually increase. + Currently this is only supported for RoundRobin and LeastRequest load balancers + properties: + window: + description: |- + Window defines the duration of the warm up period for newly added host. + During slow start window, traffic sent to the newly added hosts will gradually increase. + Currently only supports linear growth of traffic. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#config-cluster-v3-cluster-slowstartconfig + type: string + required: + - window + type: object + type: + description: |- + Type decides the type of Load Balancer policy. + Valid LoadBalancerType values are + "ConsistentHash", + "LeastRequest", + "Random", + "RoundRobin". + enum: + - ConsistentHash + - LeastRequest + - Random + - RoundRobin + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If LoadBalancer type is consistentHash, + consistentHash field needs to be set. + rule: 'self.type == ''ConsistentHash'' + ? has(self.consistentHash) : !has(self.consistentHash)' + - message: Currently SlowStart is only + supported for RoundRobin and LeastRequest + load balancers. + rule: 'self.type in [''Random'', ''ConsistentHash''] + ? !has(self.slowStart) : true ' + proxyProtocol: + description: ProxyProtocol enables the + Proxy Protocol when communicating with + the backend. + properties: + version: + description: |- + Version of ProxyProtol + Valid ProxyProtocolVersion values are + "V1" + "V2" + enum: + - V1 + - V2 + type: string + required: + - version + type: object + retry: + description: |- + Retry provides more advanced usage, allowing users to customize the number of retries, retry fallback strategy, and retry triggering conditions. + If not set, retry will be disabled. + properties: + numRetries: + default: 2 + description: NumRetries is the number + of retries to be attempted. Defaults + to 2. + format: int32 + minimum: 0 + type: integer + perRetry: + description: PerRetry is the retry + policy to be applied per retry attempt. + properties: + backOff: + description: |- + Backoff is the backoff policy to be applied per retry attempt. gateway uses a fully jittered exponential + back-off algorithm for retries. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#config-http-filters-router-x-envoy-max-retries + properties: + baseInterval: + description: BaseInterval + is the base interval between + retries. + format: duration + type: string + maxInterval: + description: |- + MaxInterval is the maximum interval between retries. This parameter is optional, but must be greater than or equal to the base_interval if set. + The default is 10 times the base_interval + format: duration + type: string + type: object + timeout: + description: Timeout is the timeout + per retry attempt. + format: duration + type: string + type: object + retryOn: + description: |- + RetryOn specifies the retry trigger condition. + + If not specified, the default is to retry on connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes(503). + properties: + httpStatusCodes: + description: |- + HttpStatusCodes specifies the http status codes to be retried. + The retriable-status-codes trigger must also be configured for these status codes to trigger a retry. + items: + description: HTTPStatus defines + the http status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + triggers: + description: Triggers specifies + the retry trigger condition(Http/Grpc). + items: + description: TriggerEnum specifies + the conditions that trigger + retries. + enum: + - 5xx + - gateway-error + - reset + - connect-failure + - retriable-4xx + - refused-stream + - retriable-status-codes + - cancelled + - deadline-exceeded + - internal + - resource-exhausted + - unavailable + type: string + type: array + type: object + type: object + tcpKeepalive: + description: |- + TcpKeepalive settings associated with the upstream client connection. + Disabled by default. + properties: + idleTime: + description: |- + The duration a connection needs to be idle before keep-alive + probes start being sent. + The duration format is + Defaults to `7200s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + The duration between keep-alive probes. + Defaults to `75s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + probes: + description: |- + The total number of unacknowledged probes to send before deciding + the connection is dead. + Defaults to 9. + format: int32 + type: integer + type: object + timeout: + description: Timeout settings for the + backend connections. + properties: + http: + description: Timeout settings for + HTTP. + properties: + connectionIdleTimeout: + description: |- + The idle timeout for an HTTP connection. Idle time is defined as a period in which there are no active requests in the connection. + Default: 1 hour. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + maxConnectionDuration: + description: |- + The maximum duration of an HTTP connection. + Default: unlimited. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + requestTimeout: + description: RequestTimeout is + the time until which entire + response is received from the + upstream. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + tcp: + description: Timeout settings for + TCP. + properties: + connectTimeout: + description: |- + The timeout for network connection establishment, including TCP and TLS handshakes. + Default: 10 seconds. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + type: object + type: object + http: + description: HTTP defines additional configuration + specific to HTTP access logs. + properties: + requestHeaders: + description: RequestHeaders defines request + headers to include in log entries sent + to the access log service. + items: + type: string + type: array + responseHeaders: + description: ResponseHeaders defines response + headers to include in log entries sent + to the access log service. + items: + type: string + type: array + responseTrailers: + description: ResponseTrailers defines + response trailers to include in log + entries sent to the access log service. + items: + type: string + type: array + type: object + logName: + description: |- + LogName defines the friendly name of the access log to be returned in + StreamAccessLogsMessage.Identifier. This allows the access log server + to differentiate between different access logs coming from the same Envoy. + minLength: 1 + type: string + type: + description: Type defines the type of accesslog. + Supported types are "HTTP" and "TCP". + enum: + - HTTP + - TCP + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: The http field may only be set when + type is HTTP. + rule: self.type == 'HTTP' || !has(self.http) + - message: BackendRefs must be used, backendRef + is not supported. + rule: '!has(self.backendRef)' + - message: must have at least one backend in backendRefs + rule: has(self.backendRefs) && self.backendRefs.size() + > 0 + - message: BackendRefs only support Service and + Backend kind. + rule: 'has(self.backendRefs) ? self.backendRefs.all(f, + f.kind == ''Service'' || f.kind == ''Backend'') + : true' + - message: BackendRefs only support Core and gateway.envoyproxy.io + group. + rule: 'has(self.backendRefs) ? (self.backendRefs.all(f, + f.group == "" || f.group == ''gateway.envoyproxy.io'')) + : true' + file: + description: File defines the file accesslog sink. + properties: + path: + description: Path defines the file path used + to expose envoy access log(e.g. /dev/stdout). + minLength: 1 + type: string + type: object + openTelemetry: + description: OpenTelemetry defines the OpenTelemetry + accesslog sink. + properties: + backendRef: + description: |- + BackendRef references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + + Deprecated: Use BackendRefs instead. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + backendRefs: + description: |- + BackendRefs references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + items: + description: BackendRef defines how an ObjectReference + that is specific to BackendRef. + properties: + fallback: + description: |- + Fallback indicates whether the backend is designated as a fallback. + Multiple fallback backends can be configured. + It is highly recommended to configure active or passive health checks to ensure that failover can be detected + when the active backends become unhealthy and to automatically readjust once the primary backends are healthy again. + The overprovisioning factor is set to 1.4, meaning the fallback backends will only start receiving traffic when + the health of the active backends falls below 72%. + type: boolean + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the + referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + maxItems: 16 + type: array + backendSettings: + description: |- + BackendSettings holds configuration for managing the connection + to the backend. + properties: + circuitBreaker: + description: |- + Circuit Breaker settings for the upstream connections and requests. + If not set, circuit breakers will be enabled with the default thresholds + properties: + maxConnections: + default: 1024 + description: The maximum number of + connections that Envoy will establish + to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRequests: + default: 1024 + description: The maximum number of + parallel requests that Envoy will + make to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRetries: + default: 1024 + description: The maximum number of + parallel retries that Envoy will + make to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxPendingRequests: + default: 1024 + description: The maximum number of + pending requests that Envoy will + queue to the referenced backend + defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxRequestsPerConnection: + description: |- + The maximum number of requests that Envoy will make over a single connection to the referenced backend defined within a xRoute rule. + Default: unlimited. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + perEndpoint: + description: PerEndpoint defines Circuit + Breakers that will apply per-endpoint + for an upstream cluster + properties: + maxConnections: + default: 1024 + description: MaxConnections configures + the maximum number of connections + that Envoy will establish per-endpoint + to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + type: object + type: object + connection: + description: Connection includes backend + connection settings. + properties: + bufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + BufferLimit Soft limit on size of the cluster’s connections read and write buffers. + BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space. + If unspecified, an implementation defined default is applied (32768 bytes). + For example, 20Mi, 1Gi, 256Ki etc. + Note: that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + socketBufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + SocketBufferLimit provides configuration for the maximum buffer size in bytes for each socket + to backend. + SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space. + For example, 20Mi, 1Gi, 256Ki etc. + Note that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + type: object + dns: + description: DNS includes dns resolution + settings. + properties: + dnsRefreshRate: + description: |- + DNSRefreshRate specifies the rate at which DNS records should be refreshed. + Defaults to 30 seconds. + type: string + lookupFamily: + description: |- + LookupFamily determines how Envoy would resolve DNS for Routes where the backend is specified as a fully qualified domain name (FQDN). + If set, this configuration overrides other defaults. + enum: + - IPv4 + - IPv6 + - IPv4Preferred + - IPv6Preferred + - IPv4AndIPv6 + type: string + respectDnsTtl: + description: |- + RespectDNSTTL indicates whether the DNS Time-To-Live (TTL) should be respected. + If the value is set to true, the DNS refresh rate will be set to the resource record’s TTL. + Defaults to true. + type: boolean + type: object + healthCheck: + description: HealthCheck allows gateway + to perform active health checking on + backends. + properties: + active: + description: Active health check configuration + properties: + grpc: + description: |- + GRPC defines the configuration of the GRPC health checker. + It's optional, and can only be used if the specified type is GRPC. + properties: + service: + description: |- + Service to send in the health check request. + If this is not specified, then the health check request applies to the entire + server and not to a specific service. + type: string + type: object + healthyThreshold: + default: 1 + description: HealthyThreshold + defines the number of healthy + health checks required before + a backend host is marked healthy. + format: int32 + minimum: 1 + type: integer + http: + description: |- + HTTP defines the configuration of http health checker. + It's required while the health checker type is HTTP. + properties: + expectedResponse: + description: ExpectedResponse + defines a list of HTTP expected + responses to match. + properties: + binary: + description: Binary payload + base64 encoded. + format: byte + type: string + text: + description: Text payload + in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines + the type of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type + is Text, text field needs + to be set. + rule: 'self.type == ''Text'' + ? has(self.text) : !has(self.text)' + - message: If payload type + is Binary, binary field + needs to be set. + rule: 'self.type == ''Binary'' + ? has(self.binary) : !has(self.binary)' + expectedStatuses: + description: |- + ExpectedStatuses defines a list of HTTP response statuses considered healthy. + Defaults to 200 only + items: + description: HTTPStatus + defines the http status + code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + method: + description: |- + Method defines the HTTP method used for health checking. + Defaults to GET + type: string + path: + description: Path defines + the HTTP path that will + be requested during health + checking. + maxLength: 1024 + minLength: 1 + type: string + required: + - path + type: object + interval: + default: 3s + description: Interval defines + the time between active health + checks. + format: duration + type: string + tcp: + description: |- + TCP defines the configuration of tcp health checker. + It's required while the health checker type is TCP. + properties: + receive: + description: Receive defines + the expected response payload. + properties: + binary: + description: Binary payload + base64 encoded. + format: byte + type: string + text: + description: Text payload + in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines + the type of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type + is Text, text field needs + to be set. + rule: 'self.type == ''Text'' + ? has(self.text) : !has(self.text)' + - message: If payload type + is Binary, binary field + needs to be set. + rule: 'self.type == ''Binary'' + ? has(self.binary) : !has(self.binary)' + send: + description: Send defines + the request payload. + properties: + binary: + description: Binary payload + base64 encoded. + format: byte + type: string + text: + description: Text payload + in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines + the type of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type + is Text, text field needs + to be set. + rule: 'self.type == ''Text'' + ? has(self.text) : !has(self.text)' + - message: If payload type + is Binary, binary field + needs to be set. + rule: 'self.type == ''Binary'' + ? has(self.binary) : !has(self.binary)' + type: object + timeout: + default: 1s + description: Timeout defines the + time to wait for a health check + response. + format: duration + type: string + type: + allOf: + - enum: + - HTTP + - TCP + - GRPC + - enum: + - HTTP + - TCP + - GRPC + description: Type defines the + type of health checker. + type: string + unhealthyThreshold: + default: 3 + description: UnhealthyThreshold + defines the number of unhealthy + health checks required before + a backend host is marked unhealthy. + format: int32 + minimum: 1 + type: integer + required: + - type + type: object + x-kubernetes-validations: + - message: If Health Checker type + is HTTP, http field needs to be + set. + rule: 'self.type == ''HTTP'' ? has(self.http) + : !has(self.http)' + - message: If Health Checker type + is TCP, tcp field needs to be + set. + rule: 'self.type == ''TCP'' ? has(self.tcp) + : !has(self.tcp)' + - message: The grpc field can only + be set if the Health Checker type + is GRPC. + rule: 'has(self.grpc) ? self.type + == ''GRPC'' : true' + panicThreshold: + description: |- + When number of unhealthy endpoints for a backend reaches this threshold + Envoy will disregard health status and balance across all endpoints. + It's designed to prevent a situation in which host failures cascade throughout the cluster + as load increases. If not set, the default value is 50%. To disable panic mode, set value to `0`. + format: int32 + maximum: 100 + minimum: 0 + type: integer + passive: + description: Passive passive check + configuration + properties: + baseEjectionTime: + default: 30s + description: BaseEjectionTime + defines the base duration for + which a host will be ejected + on consecutive failures. + format: duration + type: string + consecutive5XxErrors: + default: 5 + description: Consecutive5xxErrors + sets the number of consecutive + 5xx errors triggering ejection. + format: int32 + type: integer + consecutiveGatewayErrors: + default: 0 + description: ConsecutiveGatewayErrors + sets the number of consecutive + gateway errors triggering ejection. + format: int32 + type: integer + consecutiveLocalOriginFailures: + default: 5 + description: |- + ConsecutiveLocalOriginFailures sets the number of consecutive local origin failures triggering ejection. + Parameter takes effect only when split_external_local_origin_errors is set to true. + format: int32 + type: integer + interval: + default: 3s + description: Interval defines + the time between passive health + checks. + format: duration + type: string + maxEjectionPercent: + default: 10 + description: MaxEjectionPercent + sets the maximum percentage + of hosts in a cluster that can + be ejected. + format: int32 + type: integer + splitExternalLocalOriginErrors: + default: false + description: SplitExternalLocalOriginErrors + enables splitting of errors + between external and local origin. + type: boolean + type: object + type: object + http2: + description: HTTP2 provides HTTP/2 configuration + for backend connections. + properties: + initialConnectionWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialConnectionWindowSize sets the initial window size for HTTP/2 connections. + If not set, the default value is 1 MiB. + x-kubernetes-int-or-string: true + initialStreamWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialStreamWindowSize sets the initial window size for HTTP/2 streams. + If not set, the default value is 64 KiB(64*1024). + x-kubernetes-int-or-string: true + maxConcurrentStreams: + description: |- + MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection. + If not set, the default value is 100. + format: int32 + maximum: 2147483647 + minimum: 1 + type: integer + onInvalidMessage: + description: |- + OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error + It's recommended for L2 Envoy deployments to set this value to TerminateStream. + https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two + Default: TerminateConnection + type: string + type: object + loadBalancer: + description: |- + LoadBalancer policy to apply when routing traffic from the gateway to + the backend endpoints. Defaults to `LeastRequest`. + properties: + consistentHash: + description: |- + ConsistentHash defines the configuration when the load balancer type is + set to ConsistentHash + properties: + cookie: + description: Cookie configures + the cookie hash policy when + the consistent hash type is + set to Cookie. + properties: + attributes: + additionalProperties: + type: string + description: Additional Attributes + to set for the generated + cookie. + type: object + name: + description: |- + Name of the cookie to hash. + If this cookie does not exist in the request, Envoy will generate a cookie and set + the TTL on the response back to the client based on Layer 4 + attributes of the backend endpoint, to ensure that these future requests + go to the same backend endpoint. Make sure to set the TTL field for this case. + type: string + ttl: + description: |- + TTL of the generated cookie if the cookie is not present. This value sets the + Max-Age attribute value. + type: string + required: + - name + type: object + header: + description: Header configures + the header hash policy when + the consistent hash type is + set to Header. + properties: + name: + description: Name of the header + to hash. + type: string + required: + - name + type: object + tableSize: + default: 65537 + description: The table size for + consistent hashing, must be + prime number limited to 5000011. + format: int64 + maximum: 5000011 + minimum: 2 + type: integer + type: + description: |- + ConsistentHashType defines the type of input to hash on. Valid Type values are + "SourceIP", + "Header", + "Cookie". + enum: + - SourceIP + - Header + - Cookie + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If consistent hash type + is header, the header field must + be set. + rule: 'self.type == ''Header'' ? + has(self.header) : !has(self.header)' + - message: If consistent hash type + is cookie, the cookie field must + be set. + rule: 'self.type == ''Cookie'' ? + has(self.cookie) : !has(self.cookie)' + slowStart: + description: |- + SlowStart defines the configuration related to the slow start load balancer policy. + If set, during slow start window, traffic sent to the newly added hosts will gradually increase. + Currently this is only supported for RoundRobin and LeastRequest load balancers + properties: + window: + description: |- + Window defines the duration of the warm up period for newly added host. + During slow start window, traffic sent to the newly added hosts will gradually increase. + Currently only supports linear growth of traffic. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#config-cluster-v3-cluster-slowstartconfig + type: string + required: + - window + type: object + type: + description: |- + Type decides the type of Load Balancer policy. + Valid LoadBalancerType values are + "ConsistentHash", + "LeastRequest", + "Random", + "RoundRobin". + enum: + - ConsistentHash + - LeastRequest + - Random + - RoundRobin + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If LoadBalancer type is consistentHash, + consistentHash field needs to be set. + rule: 'self.type == ''ConsistentHash'' + ? has(self.consistentHash) : !has(self.consistentHash)' + - message: Currently SlowStart is only + supported for RoundRobin and LeastRequest + load balancers. + rule: 'self.type in [''Random'', ''ConsistentHash''] + ? !has(self.slowStart) : true ' + proxyProtocol: + description: ProxyProtocol enables the + Proxy Protocol when communicating with + the backend. + properties: + version: + description: |- + Version of ProxyProtol + Valid ProxyProtocolVersion values are + "V1" + "V2" + enum: + - V1 + - V2 + type: string + required: + - version + type: object + retry: + description: |- + Retry provides more advanced usage, allowing users to customize the number of retries, retry fallback strategy, and retry triggering conditions. + If not set, retry will be disabled. + properties: + numRetries: + default: 2 + description: NumRetries is the number + of retries to be attempted. Defaults + to 2. + format: int32 + minimum: 0 + type: integer + perRetry: + description: PerRetry is the retry + policy to be applied per retry attempt. + properties: + backOff: + description: |- + Backoff is the backoff policy to be applied per retry attempt. gateway uses a fully jittered exponential + back-off algorithm for retries. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#config-http-filters-router-x-envoy-max-retries + properties: + baseInterval: + description: BaseInterval + is the base interval between + retries. + format: duration + type: string + maxInterval: + description: |- + MaxInterval is the maximum interval between retries. This parameter is optional, but must be greater than or equal to the base_interval if set. + The default is 10 times the base_interval + format: duration + type: string + type: object + timeout: + description: Timeout is the timeout + per retry attempt. + format: duration + type: string + type: object + retryOn: + description: |- + RetryOn specifies the retry trigger condition. + + If not specified, the default is to retry on connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes(503). + properties: + httpStatusCodes: + description: |- + HttpStatusCodes specifies the http status codes to be retried. + The retriable-status-codes trigger must also be configured for these status codes to trigger a retry. + items: + description: HTTPStatus defines + the http status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + triggers: + description: Triggers specifies + the retry trigger condition(Http/Grpc). + items: + description: TriggerEnum specifies + the conditions that trigger + retries. + enum: + - 5xx + - gateway-error + - reset + - connect-failure + - retriable-4xx + - refused-stream + - retriable-status-codes + - cancelled + - deadline-exceeded + - internal + - resource-exhausted + - unavailable + type: string + type: array + type: object + type: object + tcpKeepalive: + description: |- + TcpKeepalive settings associated with the upstream client connection. + Disabled by default. + properties: + idleTime: + description: |- + The duration a connection needs to be idle before keep-alive + probes start being sent. + The duration format is + Defaults to `7200s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + The duration between keep-alive probes. + Defaults to `75s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + probes: + description: |- + The total number of unacknowledged probes to send before deciding + the connection is dead. + Defaults to 9. + format: int32 + type: integer + type: object + timeout: + description: Timeout settings for the + backend connections. + properties: + http: + description: Timeout settings for + HTTP. + properties: + connectionIdleTimeout: + description: |- + The idle timeout for an HTTP connection. Idle time is defined as a period in which there are no active requests in the connection. + Default: 1 hour. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + maxConnectionDuration: + description: |- + The maximum duration of an HTTP connection. + Default: unlimited. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + requestTimeout: + description: RequestTimeout is + the time until which entire + response is received from the + upstream. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + tcp: + description: Timeout settings for + TCP. + properties: + connectTimeout: + description: |- + The timeout for network connection establishment, including TCP and TLS handshakes. + Default: 10 seconds. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + type: object + type: object + host: + description: |- + Host define the extension service hostname. + Deprecated: Use BackendRefs instead. + type: string + port: + default: 4317 + description: |- + Port defines the port the extension service is exposed on. + Deprecated: Use BackendRefs instead. + format: int32 + minimum: 0 + type: integer + resources: + additionalProperties: + type: string + description: |- + Resources is a set of labels that describe the source of a log entry, including envoy node info. + It's recommended to follow [semantic conventions](https://opentelemetry.io/docs/reference/specification/resource/semantic_conventions/). + type: object + type: object + x-kubernetes-validations: + - message: host or backendRefs needs to be set + rule: has(self.host) || self.backendRefs.size() + > 0 + - message: BackendRefs must be used, backendRef + is not supported. + rule: '!has(self.backendRef)' + - message: BackendRefs only support Service and + Backend kind. + rule: 'has(self.backendRefs) ? self.backendRefs.all(f, + f.kind == ''Service'' || f.kind == ''Backend'') + : true' + - message: BackendRefs only support Core and gateway.envoyproxy.io + group. + rule: 'has(self.backendRefs) ? (self.backendRefs.all(f, + f.group == "" || f.group == ''gateway.envoyproxy.io'')) + : true' + type: + description: Type defines the type of accesslog + sink. + enum: + - ALS + - File + - OpenTelemetry + type: string + type: object + x-kubernetes-validations: + - message: If AccessLogSink type is ALS, als field + needs to be set. + rule: 'self.type == ''ALS'' ? has(self.als) : !has(self.als)' + - message: If AccessLogSink type is File, file field + needs to be set. + rule: 'self.type == ''File'' ? has(self.file) : + !has(self.file)' + - message: If AccessLogSink type is OpenTelemetry, + openTelemetry field needs to be set. + rule: 'self.type == ''OpenTelemetry'' ? has(self.openTelemetry) + : !has(self.openTelemetry)' + maxItems: 50 + minItems: 1 + type: array + type: + description: |- + Type defines the component emitting the accesslog, such as Listener and Route. + If type not defined, the setting would apply to: + (1) All Routes. + (2) Listeners if and only if Envoy does not find a matching route for a request. + If type is defined, the accesslog settings would apply to the relevant component (as-is). + enum: + - Listener + - Route + type: string + required: + - sinks + type: object + maxItems: 50 + minItems: 1 + type: array + type: object + metrics: + description: Metrics defines metrics configuration for managed + proxies. + properties: + enablePerEndpointStats: + description: |- + EnablePerEndpointStats enables per endpoint envoy stats metrics. + Please use with caution. + type: boolean + enableRequestResponseSizesStats: + description: EnableRequestResponseSizesStats enables publishing + of histograms tracking header and body sizes of requests + and responses. + type: boolean + enableVirtualHostStats: + description: EnableVirtualHostStats enables envoy stat metrics + for virtual hosts. + type: boolean + matches: + description: |- + Matches defines configuration for selecting specific metrics instead of generating all metrics stats + that are enabled by default. This helps reduce CPU and memory overhead in Envoy, but eliminating some stats + may after critical functionality. Here are the stats that we strongly recommend not disabling: + `cluster_manager.warming_clusters`, `cluster..membership_total`,`cluster..membership_healthy`, + `cluster..membership_degraded`,reference https://github.com/envoyproxy/envoy/issues/9856, + https://github.com/envoyproxy/envoy/issues/14610 + items: + description: |- + StringMatch defines how to match any strings. + This is a general purpose match condition that can be used by other EG APIs + that need to match against a string. + properties: + type: + default: Exact + description: Type specifies how to match against a string. + enum: + - Exact + - Prefix + - Suffix + - RegularExpression + type: string + value: + description: Value specifies the string value that the + match must have. + maxLength: 1024 + minLength: 1 + type: string + required: + - value + type: object + type: array + prometheus: + description: Prometheus defines the configuration for Admin + endpoint `/stats/prometheus`. + properties: + compression: + description: Configure the compression on Prometheus endpoint. + Compression is useful in situations when bandwidth is + scarce and large payloads can be effectively compressed + at the expense of higher CPU load. + properties: + brotli: + description: The configuration for Brotli compressor. + type: object + gzip: + description: The configuration for GZIP compressor. + type: object + type: + description: CompressorType defines the compressor + type to use for compression. + enum: + - Gzip + - Brotli + type: string + required: + - type + type: object + disable: + description: Disable the Prometheus endpoint. + type: boolean + type: object + sinks: + description: Sinks defines the metric sinks where metrics + are sent to. + items: + description: |- + ProxyMetricSink defines the sink of metrics. + Default metrics sink is OpenTelemetry. + properties: + openTelemetry: + description: |- + OpenTelemetry defines the configuration for OpenTelemetry sink. + It's required if the sink type is OpenTelemetry. + properties: + backendRef: + description: |- + BackendRef references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + + Deprecated: Use BackendRefs instead. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == + ''Service'') ? has(self.port) : true' + backendRefs: + description: |- + BackendRefs references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + items: + description: BackendRef defines how an ObjectReference + that is specific to BackendRef. + properties: + fallback: + description: |- + Fallback indicates whether the backend is designated as a fallback. + Multiple fallback backends can be configured. + It is highly recommended to configure active or passive health checks to ensure that failover can be detected + when the active backends become unhealthy and to automatically readjust once the primary backends are healthy again. + The overprovisioning factor is set to 1.4, meaning the fallback backends will only start receiving traffic when + the health of the active backends falls below 72%. + type: boolean + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == + ''Service'') ? has(self.port) : true' + maxItems: 16 + type: array + backendSettings: + description: |- + BackendSettings holds configuration for managing the connection + to the backend. + properties: + circuitBreaker: + description: |- + Circuit Breaker settings for the upstream connections and requests. + If not set, circuit breakers will be enabled with the default thresholds + properties: + maxConnections: + default: 1024 + description: The maximum number of connections + that Envoy will establish to the referenced + backend defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRequests: + default: 1024 + description: The maximum number of parallel + requests that Envoy will make to the referenced + backend defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRetries: + default: 1024 + description: The maximum number of parallel + retries that Envoy will make to the referenced + backend defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxPendingRequests: + default: 1024 + description: The maximum number of pending + requests that Envoy will queue to the + referenced backend defined within a xRoute + rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxRequestsPerConnection: + description: |- + The maximum number of requests that Envoy will make over a single connection to the referenced backend defined within a xRoute rule. + Default: unlimited. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + perEndpoint: + description: PerEndpoint defines Circuit + Breakers that will apply per-endpoint + for an upstream cluster + properties: + maxConnections: + default: 1024 + description: MaxConnections configures + the maximum number of connections + that Envoy will establish per-endpoint + to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + type: object + type: object + connection: + description: Connection includes backend connection + settings. + properties: + bufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + BufferLimit Soft limit on size of the cluster’s connections read and write buffers. + BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space. + If unspecified, an implementation defined default is applied (32768 bytes). + For example, 20Mi, 1Gi, 256Ki etc. + Note: that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + socketBufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + SocketBufferLimit provides configuration for the maximum buffer size in bytes for each socket + to backend. + SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space. + For example, 20Mi, 1Gi, 256Ki etc. + Note that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + type: object + dns: + description: DNS includes dns resolution settings. + properties: + dnsRefreshRate: + description: |- + DNSRefreshRate specifies the rate at which DNS records should be refreshed. + Defaults to 30 seconds. + type: string + lookupFamily: + description: |- + LookupFamily determines how Envoy would resolve DNS for Routes where the backend is specified as a fully qualified domain name (FQDN). + If set, this configuration overrides other defaults. + enum: + - IPv4 + - IPv6 + - IPv4Preferred + - IPv6Preferred + - IPv4AndIPv6 + type: string + respectDnsTtl: + description: |- + RespectDNSTTL indicates whether the DNS Time-To-Live (TTL) should be respected. + If the value is set to true, the DNS refresh rate will be set to the resource record’s TTL. + Defaults to true. + type: boolean + type: object + healthCheck: + description: HealthCheck allows gateway to perform + active health checking on backends. + properties: + active: + description: Active health check configuration + properties: + grpc: + description: |- + GRPC defines the configuration of the GRPC health checker. + It's optional, and can only be used if the specified type is GRPC. + properties: + service: + description: |- + Service to send in the health check request. + If this is not specified, then the health check request applies to the entire + server and not to a specific service. + type: string + type: object + healthyThreshold: + default: 1 + description: HealthyThreshold defines + the number of healthy health checks + required before a backend host is + marked healthy. + format: int32 + minimum: 1 + type: integer + http: + description: |- + HTTP defines the configuration of http health checker. + It's required while the health checker type is HTTP. + properties: + expectedResponse: + description: ExpectedResponse defines + a list of HTTP expected responses + to match. + properties: + binary: + description: Binary payload + base64 encoded. + format: byte + type: string + text: + description: Text payload in + plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the + type of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, + text field needs to be set. + rule: 'self.type == ''Text'' ? + has(self.text) : !has(self.text)' + - message: If payload type is Binary, + binary field needs to be set. + rule: 'self.type == ''Binary'' + ? has(self.binary) : !has(self.binary)' + expectedStatuses: + description: |- + ExpectedStatuses defines a list of HTTP response statuses considered healthy. + Defaults to 200 only + items: + description: HTTPStatus defines + the http status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + method: + description: |- + Method defines the HTTP method used for health checking. + Defaults to GET + type: string + path: + description: Path defines the HTTP + path that will be requested during + health checking. + maxLength: 1024 + minLength: 1 + type: string + required: + - path + type: object + interval: + default: 3s + description: Interval defines the time + between active health checks. + format: duration + type: string + tcp: + description: |- + TCP defines the configuration of tcp health checker. + It's required while the health checker type is TCP. + properties: + receive: + description: Receive defines the + expected response payload. + properties: + binary: + description: Binary payload + base64 encoded. + format: byte + type: string + text: + description: Text payload in + plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the + type of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, + text field needs to be set. + rule: 'self.type == ''Text'' ? + has(self.text) : !has(self.text)' + - message: If payload type is Binary, + binary field needs to be set. + rule: 'self.type == ''Binary'' + ? has(self.binary) : !has(self.binary)' + send: + description: Send defines the request + payload. + properties: + binary: + description: Binary payload + base64 encoded. + format: byte + type: string + text: + description: Text payload in + plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the + type of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, + text field needs to be set. + rule: 'self.type == ''Text'' ? + has(self.text) : !has(self.text)' + - message: If payload type is Binary, + binary field needs to be set. + rule: 'self.type == ''Binary'' + ? has(self.binary) : !has(self.binary)' + type: object + timeout: + default: 1s + description: Timeout defines the time + to wait for a health check response. + format: duration + type: string + type: + allOf: + - enum: + - HTTP + - TCP + - GRPC + - enum: + - HTTP + - TCP + - GRPC + description: Type defines the type of + health checker. + type: string + unhealthyThreshold: + default: 3 + description: UnhealthyThreshold defines + the number of unhealthy health checks + required before a backend host is + marked unhealthy. + format: int32 + minimum: 1 + type: integer + required: + - type + type: object + x-kubernetes-validations: + - message: If Health Checker type is HTTP, + http field needs to be set. + rule: 'self.type == ''HTTP'' ? has(self.http) + : !has(self.http)' + - message: If Health Checker type is TCP, + tcp field needs to be set. + rule: 'self.type == ''TCP'' ? has(self.tcp) + : !has(self.tcp)' + - message: The grpc field can only be set + if the Health Checker type is GRPC. + rule: 'has(self.grpc) ? self.type == ''GRPC'' + : true' + panicThreshold: + description: |- + When number of unhealthy endpoints for a backend reaches this threshold + Envoy will disregard health status and balance across all endpoints. + It's designed to prevent a situation in which host failures cascade throughout the cluster + as load increases. If not set, the default value is 50%. To disable panic mode, set value to `0`. + format: int32 + maximum: 100 + minimum: 0 + type: integer + passive: + description: Passive passive check configuration + properties: + baseEjectionTime: + default: 30s + description: BaseEjectionTime defines + the base duration for which a host + will be ejected on consecutive failures. + format: duration + type: string + consecutive5XxErrors: + default: 5 + description: Consecutive5xxErrors sets + the number of consecutive 5xx errors + triggering ejection. + format: int32 + type: integer + consecutiveGatewayErrors: + default: 0 + description: ConsecutiveGatewayErrors + sets the number of consecutive gateway + errors triggering ejection. + format: int32 + type: integer + consecutiveLocalOriginFailures: + default: 5 + description: |- + ConsecutiveLocalOriginFailures sets the number of consecutive local origin failures triggering ejection. + Parameter takes effect only when split_external_local_origin_errors is set to true. + format: int32 + type: integer + interval: + default: 3s + description: Interval defines the time + between passive health checks. + format: duration + type: string + maxEjectionPercent: + default: 10 + description: MaxEjectionPercent sets + the maximum percentage of hosts in + a cluster that can be ejected. + format: int32 + type: integer + splitExternalLocalOriginErrors: + default: false + description: SplitExternalLocalOriginErrors + enables splitting of errors between + external and local origin. + type: boolean + type: object + type: object + http2: + description: HTTP2 provides HTTP/2 configuration + for backend connections. + properties: + initialConnectionWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialConnectionWindowSize sets the initial window size for HTTP/2 connections. + If not set, the default value is 1 MiB. + x-kubernetes-int-or-string: true + initialStreamWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialStreamWindowSize sets the initial window size for HTTP/2 streams. + If not set, the default value is 64 KiB(64*1024). + x-kubernetes-int-or-string: true + maxConcurrentStreams: + description: |- + MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection. + If not set, the default value is 100. + format: int32 + maximum: 2147483647 + minimum: 1 + type: integer + onInvalidMessage: + description: |- + OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error + It's recommended for L2 Envoy deployments to set this value to TerminateStream. + https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two + Default: TerminateConnection + type: string + type: object + loadBalancer: + description: |- + LoadBalancer policy to apply when routing traffic from the gateway to + the backend endpoints. Defaults to `LeastRequest`. + properties: + consistentHash: + description: |- + ConsistentHash defines the configuration when the load balancer type is + set to ConsistentHash + properties: + cookie: + description: Cookie configures the cookie + hash policy when the consistent hash + type is set to Cookie. + properties: + attributes: + additionalProperties: + type: string + description: Additional Attributes + to set for the generated cookie. + type: object + name: + description: |- + Name of the cookie to hash. + If this cookie does not exist in the request, Envoy will generate a cookie and set + the TTL on the response back to the client based on Layer 4 + attributes of the backend endpoint, to ensure that these future requests + go to the same backend endpoint. Make sure to set the TTL field for this case. + type: string + ttl: + description: |- + TTL of the generated cookie if the cookie is not present. This value sets the + Max-Age attribute value. + type: string + required: + - name + type: object + header: + description: Header configures the header + hash policy when the consistent hash + type is set to Header. + properties: + name: + description: Name of the header + to hash. + type: string + required: + - name + type: object + tableSize: + default: 65537 + description: The table size for consistent + hashing, must be prime number limited + to 5000011. + format: int64 + maximum: 5000011 + minimum: 2 + type: integer + type: + description: |- + ConsistentHashType defines the type of input to hash on. Valid Type values are + "SourceIP", + "Header", + "Cookie". + enum: + - SourceIP + - Header + - Cookie + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If consistent hash type is header, + the header field must be set. + rule: 'self.type == ''Header'' ? has(self.header) + : !has(self.header)' + - message: If consistent hash type is cookie, + the cookie field must be set. + rule: 'self.type == ''Cookie'' ? has(self.cookie) + : !has(self.cookie)' + slowStart: + description: |- + SlowStart defines the configuration related to the slow start load balancer policy. + If set, during slow start window, traffic sent to the newly added hosts will gradually increase. + Currently this is only supported for RoundRobin and LeastRequest load balancers + properties: + window: + description: |- + Window defines the duration of the warm up period for newly added host. + During slow start window, traffic sent to the newly added hosts will gradually increase. + Currently only supports linear growth of traffic. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#config-cluster-v3-cluster-slowstartconfig + type: string + required: + - window + type: object + type: + description: |- + Type decides the type of Load Balancer policy. + Valid LoadBalancerType values are + "ConsistentHash", + "LeastRequest", + "Random", + "RoundRobin". + enum: + - ConsistentHash + - LeastRequest + - Random + - RoundRobin + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If LoadBalancer type is consistentHash, + consistentHash field needs to be set. + rule: 'self.type == ''ConsistentHash'' ? has(self.consistentHash) + : !has(self.consistentHash)' + - message: Currently SlowStart is only supported + for RoundRobin and LeastRequest load balancers. + rule: 'self.type in [''Random'', ''ConsistentHash''] + ? !has(self.slowStart) : true ' + proxyProtocol: + description: ProxyProtocol enables the Proxy + Protocol when communicating with the backend. + properties: + version: + description: |- + Version of ProxyProtol + Valid ProxyProtocolVersion values are + "V1" + "V2" + enum: + - V1 + - V2 + type: string + required: + - version + type: object + retry: + description: |- + Retry provides more advanced usage, allowing users to customize the number of retries, retry fallback strategy, and retry triggering conditions. + If not set, retry will be disabled. + properties: + numRetries: + default: 2 + description: NumRetries is the number of + retries to be attempted. Defaults to 2. + format: int32 + minimum: 0 + type: integer + perRetry: + description: PerRetry is the retry policy + to be applied per retry attempt. + properties: + backOff: + description: |- + Backoff is the backoff policy to be applied per retry attempt. gateway uses a fully jittered exponential + back-off algorithm for retries. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#config-http-filters-router-x-envoy-max-retries + properties: + baseInterval: + description: BaseInterval is the + base interval between retries. + format: duration + type: string + maxInterval: + description: |- + MaxInterval is the maximum interval between retries. This parameter is optional, but must be greater than or equal to the base_interval if set. + The default is 10 times the base_interval + format: duration + type: string + type: object + timeout: + description: Timeout is the timeout + per retry attempt. + format: duration + type: string + type: object + retryOn: + description: |- + RetryOn specifies the retry trigger condition. + + If not specified, the default is to retry on connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes(503). + properties: + httpStatusCodes: + description: |- + HttpStatusCodes specifies the http status codes to be retried. + The retriable-status-codes trigger must also be configured for these status codes to trigger a retry. + items: + description: HTTPStatus defines the + http status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + triggers: + description: Triggers specifies the + retry trigger condition(Http/Grpc). + items: + description: TriggerEnum specifies + the conditions that trigger retries. + enum: + - 5xx + - gateway-error + - reset + - connect-failure + - retriable-4xx + - refused-stream + - retriable-status-codes + - cancelled + - deadline-exceeded + - internal + - resource-exhausted + - unavailable + type: string + type: array + type: object + type: object + tcpKeepalive: + description: |- + TcpKeepalive settings associated with the upstream client connection. + Disabled by default. + properties: + idleTime: + description: |- + The duration a connection needs to be idle before keep-alive + probes start being sent. + The duration format is + Defaults to `7200s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + The duration between keep-alive probes. + Defaults to `75s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + probes: + description: |- + The total number of unacknowledged probes to send before deciding + the connection is dead. + Defaults to 9. + format: int32 + type: integer + type: object + timeout: + description: Timeout settings for the backend + connections. + properties: + http: + description: Timeout settings for HTTP. + properties: + connectionIdleTimeout: + description: |- + The idle timeout for an HTTP connection. Idle time is defined as a period in which there are no active requests in the connection. + Default: 1 hour. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + maxConnectionDuration: + description: |- + The maximum duration of an HTTP connection. + Default: unlimited. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + requestTimeout: + description: RequestTimeout is the time + until which entire response is received + from the upstream. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + tcp: + description: Timeout settings for TCP. + properties: + connectTimeout: + description: |- + The timeout for network connection establishment, including TCP and TLS handshakes. + Default: 10 seconds. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + type: object + type: object + host: + description: |- + Host define the service hostname. + Deprecated: Use BackendRefs instead. + type: string + port: + default: 4317 + description: |- + Port defines the port the service is exposed on. + Deprecated: Use BackendRefs instead. + format: int32 + maximum: 65535 + minimum: 0 + type: integer + type: object + x-kubernetes-validations: + - message: host or backendRefs needs to be set + rule: has(self.host) || self.backendRefs.size() > + 0 + - message: BackendRefs must be used, backendRef is not + supported. + rule: '!has(self.backendRef)' + - message: BackendRefs only support Service and Backend + kind. + rule: 'has(self.backendRefs) ? self.backendRefs.all(f, + f.kind == ''Service'' || f.kind == ''Backend'') + : true' + - message: BackendRefs only support Core and gateway.envoyproxy.io + group. + rule: 'has(self.backendRefs) ? (self.backendRefs.all(f, + f.group == "" || f.group == ''gateway.envoyproxy.io'')) + : true' + type: + default: OpenTelemetry + description: |- + Type defines the metric sink type. + EG currently only supports OpenTelemetry. + enum: + - OpenTelemetry + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If MetricSink type is OpenTelemetry, openTelemetry + field needs to be set. + rule: 'self.type == ''OpenTelemetry'' ? has(self.openTelemetry) + : !has(self.openTelemetry)' + maxItems: 16 + type: array + type: object + tracing: + description: |- + Tracing defines tracing configuration for managed proxies. + If unspecified, will not send tracing data. + properties: + customTags: + additionalProperties: + properties: + environment: + description: |- + Environment adds value from environment variable to each span. + It's required when the type is "Environment". + properties: + defaultValue: + description: DefaultValue defines the default value + to use if the environment variable is not set. + type: string + name: + description: Name defines the name of the environment + variable which to extract the value from. + type: string + required: + - name + type: object + literal: + description: |- + Literal adds hard-coded value to each span. + It's required when the type is "Literal". + properties: + value: + description: Value defines the hard-coded value + to add to each span. + type: string + required: + - value + type: object + requestHeader: + description: |- + RequestHeader adds value from request header to each span. + It's required when the type is "RequestHeader". + properties: + defaultValue: + description: DefaultValue defines the default value + to use if the request header is not set. + type: string + name: + description: Name defines the name of the request + header which to extract the value from. + type: string + required: + - name + type: object + type: + default: Literal + description: Type defines the type of custom tag. + enum: + - Literal + - Environment + - RequestHeader + type: string + required: + - type + type: object + description: |- + CustomTags defines the custom tags to add to each span. + If provider is kubernetes, pod name and namespace are added by default. + type: object + provider: + description: Provider defines the tracing provider. + properties: + backendRef: + description: |- + BackendRef references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + + Deprecated: Use BackendRefs instead. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + backendRefs: + description: |- + BackendRefs references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + items: + description: BackendRef defines how an ObjectReference + that is specific to BackendRef. + properties: + fallback: + description: |- + Fallback indicates whether the backend is designated as a fallback. + Multiple fallback backends can be configured. + It is highly recommended to configure active or passive health checks to ensure that failover can be detected + when the active backends become unhealthy and to automatically readjust once the primary backends are healthy again. + The overprovisioning factor is set to 1.4, meaning the fallback backends will only start receiving traffic when + the health of the active backends falls below 72%. + type: boolean + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + backendSettings: + description: |- + BackendSettings holds configuration for managing the connection + to the backend. + properties: + circuitBreaker: + description: |- + Circuit Breaker settings for the upstream connections and requests. + If not set, circuit breakers will be enabled with the default thresholds + properties: + maxConnections: + default: 1024 + description: The maximum number of connections + that Envoy will establish to the referenced + backend defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRequests: + default: 1024 + description: The maximum number of parallel requests + that Envoy will make to the referenced backend + defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRetries: + default: 1024 + description: The maximum number of parallel retries + that Envoy will make to the referenced backend + defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxPendingRequests: + default: 1024 + description: The maximum number of pending requests + that Envoy will queue to the referenced backend + defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxRequestsPerConnection: + description: |- + The maximum number of requests that Envoy will make over a single connection to the referenced backend defined within a xRoute rule. + Default: unlimited. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + perEndpoint: + description: PerEndpoint defines Circuit Breakers + that will apply per-endpoint for an upstream + cluster + properties: + maxConnections: + default: 1024 + description: MaxConnections configures the + maximum number of connections that Envoy + will establish per-endpoint to the referenced + backend defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + type: object + type: object + connection: + description: Connection includes backend connection + settings. + properties: + bufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + BufferLimit Soft limit on size of the cluster’s connections read and write buffers. + BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space. + If unspecified, an implementation defined default is applied (32768 bytes). + For example, 20Mi, 1Gi, 256Ki etc. + Note: that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + socketBufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + SocketBufferLimit provides configuration for the maximum buffer size in bytes for each socket + to backend. + SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space. + For example, 20Mi, 1Gi, 256Ki etc. + Note that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + type: object + dns: + description: DNS includes dns resolution settings. + properties: + dnsRefreshRate: + description: |- + DNSRefreshRate specifies the rate at which DNS records should be refreshed. + Defaults to 30 seconds. + type: string + lookupFamily: + description: |- + LookupFamily determines how Envoy would resolve DNS for Routes where the backend is specified as a fully qualified domain name (FQDN). + If set, this configuration overrides other defaults. + enum: + - IPv4 + - IPv6 + - IPv4Preferred + - IPv6Preferred + - IPv4AndIPv6 + type: string + respectDnsTtl: + description: |- + RespectDNSTTL indicates whether the DNS Time-To-Live (TTL) should be respected. + If the value is set to true, the DNS refresh rate will be set to the resource record’s TTL. + Defaults to true. + type: boolean + type: object + healthCheck: + description: HealthCheck allows gateway to perform + active health checking on backends. + properties: + active: + description: Active health check configuration + properties: + grpc: + description: |- + GRPC defines the configuration of the GRPC health checker. + It's optional, and can only be used if the specified type is GRPC. + properties: + service: + description: |- + Service to send in the health check request. + If this is not specified, then the health check request applies to the entire + server and not to a specific service. + type: string + type: object + healthyThreshold: + default: 1 + description: HealthyThreshold defines the + number of healthy health checks required + before a backend host is marked healthy. + format: int32 + minimum: 1 + type: integer + http: + description: |- + HTTP defines the configuration of http health checker. + It's required while the health checker type is HTTP. + properties: + expectedResponse: + description: ExpectedResponse defines + a list of HTTP expected responses to + match. + properties: + binary: + description: Binary payload base64 + encoded. + format: byte + type: string + text: + description: Text payload in plain + text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type + of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text + field needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, + binary field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + expectedStatuses: + description: |- + ExpectedStatuses defines a list of HTTP response statuses considered healthy. + Defaults to 200 only + items: + description: HTTPStatus defines the + http status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + method: + description: |- + Method defines the HTTP method used for health checking. + Defaults to GET + type: string + path: + description: Path defines the HTTP path + that will be requested during health + checking. + maxLength: 1024 + minLength: 1 + type: string + required: + - path + type: object + interval: + default: 3s + description: Interval defines the time between + active health checks. + format: duration + type: string + tcp: + description: |- + TCP defines the configuration of tcp health checker. + It's required while the health checker type is TCP. + properties: + receive: + description: Receive defines the expected + response payload. + properties: + binary: + description: Binary payload base64 + encoded. + format: byte + type: string + text: + description: Text payload in plain + text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type + of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text + field needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, + binary field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + send: + description: Send defines the request + payload. + properties: + binary: + description: Binary payload base64 + encoded. + format: byte + type: string + text: + description: Text payload in plain + text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type + of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text + field needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, + binary field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + type: object + timeout: + default: 1s + description: Timeout defines the time to wait + for a health check response. + format: duration + type: string + type: + allOf: + - enum: + - HTTP + - TCP + - GRPC + - enum: + - HTTP + - TCP + - GRPC + description: Type defines the type of health + checker. + type: string + unhealthyThreshold: + default: 3 + description: UnhealthyThreshold defines the + number of unhealthy health checks required + before a backend host is marked unhealthy. + format: int32 + minimum: 1 + type: integer + required: + - type + type: object + x-kubernetes-validations: + - message: If Health Checker type is HTTP, http + field needs to be set. + rule: 'self.type == ''HTTP'' ? has(self.http) + : !has(self.http)' + - message: If Health Checker type is TCP, tcp + field needs to be set. + rule: 'self.type == ''TCP'' ? has(self.tcp) + : !has(self.tcp)' + - message: The grpc field can only be set if the + Health Checker type is GRPC. + rule: 'has(self.grpc) ? self.type == ''GRPC'' + : true' + panicThreshold: + description: |- + When number of unhealthy endpoints for a backend reaches this threshold + Envoy will disregard health status and balance across all endpoints. + It's designed to prevent a situation in which host failures cascade throughout the cluster + as load increases. If not set, the default value is 50%. To disable panic mode, set value to `0`. + format: int32 + maximum: 100 + minimum: 0 + type: integer + passive: + description: Passive passive check configuration + properties: + baseEjectionTime: + default: 30s + description: BaseEjectionTime defines the + base duration for which a host will be ejected + on consecutive failures. + format: duration + type: string + consecutive5XxErrors: + default: 5 + description: Consecutive5xxErrors sets the + number of consecutive 5xx errors triggering + ejection. + format: int32 + type: integer + consecutiveGatewayErrors: + default: 0 + description: ConsecutiveGatewayErrors sets + the number of consecutive gateway errors + triggering ejection. + format: int32 + type: integer + consecutiveLocalOriginFailures: + default: 5 + description: |- + ConsecutiveLocalOriginFailures sets the number of consecutive local origin failures triggering ejection. + Parameter takes effect only when split_external_local_origin_errors is set to true. + format: int32 + type: integer + interval: + default: 3s + description: Interval defines the time between + passive health checks. + format: duration + type: string + maxEjectionPercent: + default: 10 + description: MaxEjectionPercent sets the maximum + percentage of hosts in a cluster that can + be ejected. + format: int32 + type: integer + splitExternalLocalOriginErrors: + default: false + description: SplitExternalLocalOriginErrors + enables splitting of errors between external + and local origin. + type: boolean + type: object + type: object + http2: + description: HTTP2 provides HTTP/2 configuration for + backend connections. + properties: + initialConnectionWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialConnectionWindowSize sets the initial window size for HTTP/2 connections. + If not set, the default value is 1 MiB. + x-kubernetes-int-or-string: true + initialStreamWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialStreamWindowSize sets the initial window size for HTTP/2 streams. + If not set, the default value is 64 KiB(64*1024). + x-kubernetes-int-or-string: true + maxConcurrentStreams: + description: |- + MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection. + If not set, the default value is 100. + format: int32 + maximum: 2147483647 + minimum: 1 + type: integer + onInvalidMessage: + description: |- + OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error + It's recommended for L2 Envoy deployments to set this value to TerminateStream. + https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two + Default: TerminateConnection + type: string + type: object + loadBalancer: + description: |- + LoadBalancer policy to apply when routing traffic from the gateway to + the backend endpoints. Defaults to `LeastRequest`. + properties: + consistentHash: + description: |- + ConsistentHash defines the configuration when the load balancer type is + set to ConsistentHash + properties: + cookie: + description: Cookie configures the cookie + hash policy when the consistent hash type + is set to Cookie. + properties: + attributes: + additionalProperties: + type: string + description: Additional Attributes to + set for the generated cookie. + type: object + name: + description: |- + Name of the cookie to hash. + If this cookie does not exist in the request, Envoy will generate a cookie and set + the TTL on the response back to the client based on Layer 4 + attributes of the backend endpoint, to ensure that these future requests + go to the same backend endpoint. Make sure to set the TTL field for this case. + type: string + ttl: + description: |- + TTL of the generated cookie if the cookie is not present. This value sets the + Max-Age attribute value. + type: string + required: + - name + type: object + header: + description: Header configures the header + hash policy when the consistent hash type + is set to Header. + properties: + name: + description: Name of the header to hash. + type: string + required: + - name + type: object + tableSize: + default: 65537 + description: The table size for consistent + hashing, must be prime number limited to + 5000011. + format: int64 + maximum: 5000011 + minimum: 2 + type: integer + type: + description: |- + ConsistentHashType defines the type of input to hash on. Valid Type values are + "SourceIP", + "Header", + "Cookie". + enum: + - SourceIP + - Header + - Cookie + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If consistent hash type is header, + the header field must be set. + rule: 'self.type == ''Header'' ? has(self.header) + : !has(self.header)' + - message: If consistent hash type is cookie, + the cookie field must be set. + rule: 'self.type == ''Cookie'' ? has(self.cookie) + : !has(self.cookie)' + slowStart: + description: |- + SlowStart defines the configuration related to the slow start load balancer policy. + If set, during slow start window, traffic sent to the newly added hosts will gradually increase. + Currently this is only supported for RoundRobin and LeastRequest load balancers + properties: + window: + description: |- + Window defines the duration of the warm up period for newly added host. + During slow start window, traffic sent to the newly added hosts will gradually increase. + Currently only supports linear growth of traffic. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#config-cluster-v3-cluster-slowstartconfig + type: string + required: + - window + type: object + type: + description: |- + Type decides the type of Load Balancer policy. + Valid LoadBalancerType values are + "ConsistentHash", + "LeastRequest", + "Random", + "RoundRobin". + enum: + - ConsistentHash + - LeastRequest + - Random + - RoundRobin + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If LoadBalancer type is consistentHash, + consistentHash field needs to be set. + rule: 'self.type == ''ConsistentHash'' ? has(self.consistentHash) + : !has(self.consistentHash)' + - message: Currently SlowStart is only supported for + RoundRobin and LeastRequest load balancers. + rule: 'self.type in [''Random'', ''ConsistentHash''] + ? !has(self.slowStart) : true ' + proxyProtocol: + description: ProxyProtocol enables the Proxy Protocol + when communicating with the backend. + properties: + version: + description: |- + Version of ProxyProtol + Valid ProxyProtocolVersion values are + "V1" + "V2" + enum: + - V1 + - V2 + type: string + required: + - version + type: object + retry: + description: |- + Retry provides more advanced usage, allowing users to customize the number of retries, retry fallback strategy, and retry triggering conditions. + If not set, retry will be disabled. + properties: + numRetries: + default: 2 + description: NumRetries is the number of retries + to be attempted. Defaults to 2. + format: int32 + minimum: 0 + type: integer + perRetry: + description: PerRetry is the retry policy to be + applied per retry attempt. + properties: + backOff: + description: |- + Backoff is the backoff policy to be applied per retry attempt. gateway uses a fully jittered exponential + back-off algorithm for retries. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#config-http-filters-router-x-envoy-max-retries + properties: + baseInterval: + description: BaseInterval is the base + interval between retries. + format: duration + type: string + maxInterval: + description: |- + MaxInterval is the maximum interval between retries. This parameter is optional, but must be greater than or equal to the base_interval if set. + The default is 10 times the base_interval + format: duration + type: string + type: object + timeout: + description: Timeout is the timeout per retry + attempt. + format: duration + type: string + type: object + retryOn: + description: |- + RetryOn specifies the retry trigger condition. + + If not specified, the default is to retry on connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes(503). + properties: + httpStatusCodes: + description: |- + HttpStatusCodes specifies the http status codes to be retried. + The retriable-status-codes trigger must also be configured for these status codes to trigger a retry. + items: + description: HTTPStatus defines the http + status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + triggers: + description: Triggers specifies the retry + trigger condition(Http/Grpc). + items: + description: TriggerEnum specifies the conditions + that trigger retries. + enum: + - 5xx + - gateway-error + - reset + - connect-failure + - retriable-4xx + - refused-stream + - retriable-status-codes + - cancelled + - deadline-exceeded + - internal + - resource-exhausted + - unavailable + type: string + type: array + type: object + type: object + tcpKeepalive: + description: |- + TcpKeepalive settings associated with the upstream client connection. + Disabled by default. + properties: + idleTime: + description: |- + The duration a connection needs to be idle before keep-alive + probes start being sent. + The duration format is + Defaults to `7200s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + The duration between keep-alive probes. + Defaults to `75s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + probes: + description: |- + The total number of unacknowledged probes to send before deciding + the connection is dead. + Defaults to 9. + format: int32 + type: integer + type: object + timeout: + description: Timeout settings for the backend connections. + properties: + http: + description: Timeout settings for HTTP. + properties: + connectionIdleTimeout: + description: |- + The idle timeout for an HTTP connection. Idle time is defined as a period in which there are no active requests in the connection. + Default: 1 hour. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + maxConnectionDuration: + description: |- + The maximum duration of an HTTP connection. + Default: unlimited. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + requestTimeout: + description: RequestTimeout is the time until + which entire response is received from the + upstream. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + tcp: + description: Timeout settings for TCP. + properties: + connectTimeout: + description: |- + The timeout for network connection establishment, including TCP and TLS handshakes. + Default: 10 seconds. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + type: object + type: object + host: + description: |- + Host define the provider service hostname. + Deprecated: Use BackendRefs instead. + type: string + port: + default: 4317 + description: |- + Port defines the port the provider service is exposed on. + Deprecated: Use BackendRefs instead. + format: int32 + minimum: 0 + type: integer + type: + default: OpenTelemetry + description: Type defines the tracing provider type. + enum: + - OpenTelemetry + - Zipkin + - Datadog + type: string + zipkin: + description: Zipkin defines the Zipkin tracing provider + configuration + properties: + disableSharedSpanContext: + description: |- + DisableSharedSpanContext determines whether the default Envoy behaviour of + client and server spans sharing the same span context should be disabled. + type: boolean + enable128BitTraceId: + description: |- + Enable128BitTraceID determines whether a 128bit trace id will be used + when creating a new trace instance. If set to false, a 64bit trace + id will be used. + type: boolean + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: host or backendRefs needs to be set + rule: has(self.host) || self.backendRefs.size() > 0 + - message: BackendRefs must be used, backendRef is not supported. + rule: '!has(self.backendRef)' + - message: BackendRefs only support Service and Backend kind. + rule: 'has(self.backendRefs) ? self.backendRefs.all(f, f.kind + == ''Service'' || f.kind == ''Backend'') : true' + - message: BackendRefs only support Core and gateway.envoyproxy.io + group. + rule: 'has(self.backendRefs) ? (self.backendRefs.all(f, + f.group == "" || f.group == ''gateway.envoyproxy.io'')) + : true' + samplingFraction: + description: |- + SamplingFraction represents the fraction of requests that should be + selected for tracing if no prior sampling decision has been made. + + Only one of SamplingRate or SamplingFraction may be specified. + If neither field is specified, all requests will be sampled. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal to denominator + rule: self.numerator <= self.denominator + samplingRate: + description: |- + SamplingRate controls the rate at which traffic will be + selected for tracing if no prior sampling decision has been made. + Defaults to 100, valid values [0-100]. 100 indicates 100% sampling. + + Only one of SamplingRate or SamplingFraction may be specified. + If neither field is specified, all requests will be sampled. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - provider + type: object + x-kubernetes-validations: + - message: only one of SamplingRate or SamplingFraction can be + specified + rule: '!(has(self.samplingRate) && has(self.samplingFraction))' + type: object + type: object + status: + description: EnvoyProxyStatus defines the actual state of EnvoyProxy. + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: gateway-crds-helm/templates/generated/gateway.envoyproxy.io_httproutefilters.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 + name: httproutefilters.gateway.envoyproxy.io +spec: + group: gateway.envoyproxy.io + names: + categories: + - envoy-gateway + kind: HTTPRouteFilter + listKind: HTTPRouteFilterList + plural: httproutefilters + shortNames: + - hrf + singular: httproutefilter + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + HTTPRouteFilter is a custom Envoy Gateway HTTPRouteFilter which provides extended + traffic processing options such as path regex rewrite, direct response and more. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of HTTPRouteFilter. + properties: + credentialInjection: + description: |- + HTTPCredentialInjectionFilter defines the configuration to inject credentials into the request. + This is useful when the backend service requires credentials in the request, and the original + request does not contain them. The filter can inject credentials into the request before forwarding + it to the backend service. + properties: + credential: + description: Credential is the credential to be injected. + properties: + valueRef: + description: |- + ValueRef is a reference to the secret containing the credentials to be injected. + This is an Opaque secret. The credential should be stored in the key + "credential", and the value should be the credential to be injected. + For example, for basic authentication, the value should be "Basic ". + for bearer token, the value should be "Bearer ". + Note: The secret must be in the same namespace as the HTTPRouteFilter. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example + "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + required: + - valueRef + type: object + header: + description: |- + Header is the name of the header where the credentials are injected. + If not specified, the credentials are injected into the Authorization header. + type: string + overwrite: + description: |- + Whether to overwrite the value or not if the injected headers already exist. + If not specified, the default value is false. + type: boolean + required: + - credential + type: object + directResponse: + description: HTTPDirectResponseFilter defines the configuration to + return a fixed response. + properties: + body: + description: Body of the Response + properties: + inline: + description: Inline contains the value as an inline string. + type: string + type: + allOf: + - enum: + - Inline + - ValueRef + - enum: + - Inline + - ValueRef + default: Inline + description: |- + Type is the type of method to use to read the body value. + Valid values are Inline and ValueRef, default is Inline. + type: string + valueRef: + description: |- + ValueRef contains the contents of the body + specified as a local object reference. + Only a reference to ConfigMap is supported. + + The value of key `response.body` in the ConfigMap will be used as the response body. + If the key is not found, the first value in the ConfigMap will be used. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: inline must be set for type Inline + rule: '(!has(self.type) || self.type == ''Inline'')? has(self.inline) + : true' + - message: valueRef must be set for type ValueRef + rule: '(has(self.type) && self.type == ''ValueRef'')? has(self.valueRef) + : true' + - message: only ConfigMap is supported for ValueRef + rule: 'has(self.valueRef) ? self.valueRef.kind == ''ConfigMap'' + : true' + contentType: + description: Content Type of the response. This will be set in + the Content-Type header. + type: string + statusCode: + description: |- + Status Code of the HTTP response + If unset, defaults to 200. + type: integer + type: object + urlRewrite: + description: HTTPURLRewriteFilter define rewrites of HTTP URL components + such as path and host + properties: + hostname: + description: |- + Hostname is the value to be used to replace the Host header value during + forwarding. + properties: + header: + description: Header is the name of the header whose value + would be used to rewrite the Host header + type: string + type: + description: HTTPPathModifierType defines the type of Hostname + rewrite. + enum: + - Header + - Backend + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: header must be nil if the type is not Header + rule: '!(has(self.header) && self.type != ''Header'')' + - message: header must be specified for Header type + rule: '!(!has(self.header) && self.type == ''Header'')' + path: + description: Path defines a path rewrite. + properties: + replaceRegexMatch: + description: |- + ReplaceRegexMatch defines a path regex rewrite. The path portions matched by the regex pattern are replaced by the defined substitution. + https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-regex-rewrite + Some examples: + (1) replaceRegexMatch: + pattern: ^/service/([^/]+)(/.*)$ + substitution: \2/instance/\1 + Would transform /service/foo/v1/api into /v1/api/instance/foo. + (2) replaceRegexMatch: + pattern: one + substitution: two + Would transform /xxx/one/yyy/one/zzz into /xxx/two/yyy/two/zzz. + (3) replaceRegexMatch: + pattern: ^(.*?)one(.*)$ + substitution: \1two\2 + Would transform /xxx/one/yyy/one/zzz into /xxx/two/yyy/one/zzz. + (3) replaceRegexMatch: + pattern: (?i)/xxx/ + substitution: /yyy/ + Would transform path /aaa/XxX/bbb into /aaa/yyy/bbb (case-insensitive). + properties: + pattern: + description: |- + Pattern matches a regular expression against the value of the HTTP Path.The regex string must + adhere to the syntax documented in https://github.com/google/re2/wiki/Syntax. + minLength: 1 + type: string + substitution: + description: |- + Substitution is an expression that replaces the matched portion.The expression may include numbered + capture groups that adhere to syntax documented in https://github.com/google/re2/wiki/Syntax. + type: string + required: + - pattern + - substitution + type: object + type: + description: HTTPPathModifierType defines the type of path + redirect or rewrite. + enum: + - ReplaceRegexMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If HTTPPathModifier type is ReplaceRegexMatch, replaceRegexMatch + field needs to be set. + rule: 'self.type == ''ReplaceRegexMatch'' ? has(self.replaceRegexMatch) + : !has(self.replaceRegexMatch)' + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: {} +--- +# Source: gateway-crds-helm/templates/generated/gateway.envoyproxy.io_securitypolicies.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 + name: securitypolicies.gateway.envoyproxy.io +spec: + group: gateway.envoyproxy.io + names: + categories: + - envoy-gateway + kind: SecurityPolicy + listKind: SecurityPolicyList + plural: securitypolicies + shortNames: + - sp + singular: securitypolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + SecurityPolicy allows the user to configure various security settings for a + Gateway. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of SecurityPolicy. + properties: + apiKeyAuth: + description: APIKeyAuth defines the configuration for the API Key + Authentication. + properties: + credentialRefs: + description: |- + CredentialRefs is the Kubernetes secret which contains the API keys. + This is an Opaque secret. + Each API key is stored in the key representing the client id. + If the secrets have a key for a duplicated client, the first one will be used. + items: + description: |- + SecretObjectReference identifies an API object including its namespace, + defaulting to Secret. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + type: array + extractFrom: + description: |- + ExtractFrom is where to fetch the key from the coming request. + The value from the first source that has a key will be used. + items: + description: |- + ExtractFrom is where to fetch the key from the coming request. + Only one of header, param or cookie is supposed to be specified. + properties: + cookies: + description: |- + Cookies is the names of the cookie to fetch the key from. + If multiple cookies are specified, envoy will look for the api key in the order of the list. + This field is optional, but only one of headers, params or cookies is supposed to be specified. + items: + type: string + type: array + headers: + description: |- + Headers is the names of the header to fetch the key from. + If multiple headers are specified, envoy will look for the api key in the order of the list. + This field is optional, but only one of headers, params or cookies is supposed to be specified. + items: + type: string + type: array + params: + description: |- + Params is the names of the query parameter to fetch the key from. + If multiple params are specified, envoy will look for the api key in the order of the list. + This field is optional, but only one of headers, params or cookies is supposed to be specified. + items: + type: string + type: array + type: object + type: array + required: + - credentialRefs + - extractFrom + type: object + authorization: + description: Authorization defines the authorization configuration. + properties: + defaultAction: + description: |- + DefaultAction defines the default action to be taken if no rules match. + If not specified, the default action is Deny. + enum: + - Allow + - Deny + type: string + rules: + description: |- + Rules defines a list of authorization rules. + These rules are evaluated in order, the first matching rule will be applied, + and the rest will be skipped. + + For example, if there are two rules: the first rule allows the request + and the second rule denies it, when a request matches both rules, it will be allowed. + items: + description: AuthorizationRule defines a single authorization + rule. + properties: + action: + description: Action defines the action to be taken if the + rule matches. + enum: + - Allow + - Deny + type: string + name: + description: |- + Name is a user-friendly name for the rule. + If not specified, Envoy Gateway will generate a unique name for the rule. + maxLength: 253 + minLength: 1 + type: string + operation: + description: |- + Operation specifies the operation of a request, such as HTTP methods. + If not specified, all operations are matched on. + properties: + methods: + description: |- + Methods are the HTTP methods of the request. + If multiple methods are specified, all specified methods are allowed or denied, based on the action of the rule. + items: + description: |- + HTTPMethod describes how to select a HTTP route by matching the HTTP + method as defined by + [RFC 7231](https://datatracker.ietf.org/doc/html/rfc7231#section-4) and + [RFC 5789](https://datatracker.ietf.org/doc/html/rfc5789#section-2). + The value is expected in upper case. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + maxItems: 16 + minItems: 1 + type: array + required: + - methods + type: object + principal: + description: |- + Principal specifies the client identity of a request. + If there are multiple principal types, all principals must match for the rule to match. + For example, if there are two principals: one for client IP and one for JWT claim, + the rule will match only if both the client IP and the JWT claim match. + properties: + clientCIDRs: + description: |- + ClientCIDRs are the IP CIDR ranges of the client. + Valid examples are "192.168.1.0/24" or "2001:db8::/64" + + If multiple CIDR ranges are specified, one of the CIDR ranges must match + the client IP for the rule to match. + + The client IP is inferred from the X-Forwarded-For header, a custom header, + or the proxy protocol. + You can use the `ClientIPDetection` or the `EnableProxyProtocol` field in + the `ClientTrafficPolicy` to configure how the client IP is detected. + items: + description: |- + CIDR defines a CIDR Address range. + A CIDR can be an IPv4 address range such as "192.168.1.0/24" or an IPv6 address range such as "2001:0db8:11a3:09d7::/64". + pattern: ((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/([0-9]+))|((([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/([0-9]+)) + type: string + minItems: 1 + type: array + headers: + description: |- + Headers authorize the request based on user identity extracted from custom headers. + If multiple headers are specified, all headers must match for the rule to match. + items: + description: AuthorizationHeaderMatch specifies how + to match against the value of an HTTP header within + a authorization rule. + properties: + name: + description: |- + Name of the HTTP header. + The header name is case-insensitive unless PreserveHeaderCase is set to true. + For example, "Foo" and "foo" are considered the same header. + maxLength: 256 + minLength: 1 + type: string + values: + description: |- + Values are the values that the header must match. + If multiple values are specified, the rule will match if any of the values match. + items: + type: string + maxItems: 256 + minItems: 1 + type: array + required: + - name + - values + type: object + maxItems: 256 + minItems: 1 + type: array + jwt: + description: |- + JWT authorize the request based on the JWT claims and scopes. + Note: in order to use JWT claims for authorization, you must configure the + JWT authentication in the same `SecurityPolicy`. + properties: + claims: + description: |- + Claims are the claims in a JWT token. + + If multiple claims are specified, all claims must match for the rule to match. + For example, if there are two claims: one for the audience and one for the issuer, + the rule will match only if both the audience and the issuer match. + items: + description: JWTClaim specifies a claim in a JWT + token. + properties: + name: + description: |- + Name is the name of the claim. + If it is a nested claim, use a dot (.) separated string as the name to + represent the full path to the claim. + For example, if the claim is in the "department" field in the "organization" field, + the name should be "organization.department". + maxLength: 253 + minLength: 1 + type: string + valueType: + default: String + description: |- + ValueType is the type of the claim value. + Only String and StringArray types are supported for now. + enum: + - String + - StringArray + type: string + values: + description: |- + Values are the values that the claim must match. + If the claim is a string type, the specified value must match exactly. + If the claim is a string array type, the specified value must match one of the values in the array. + If multiple values are specified, one of the values must match for the rule to match. + items: + type: string + maxItems: 16 + minItems: 1 + type: array + required: + - name + - values + type: object + maxItems: 16 + minItems: 1 + type: array + provider: + description: |- + Provider is the name of the JWT provider that used to verify the JWT token. + In order to use JWT claims for authorization, you must configure the JWT + authentication with the same provider in the same `SecurityPolicy`. + maxLength: 253 + minLength: 1 + type: string + scopes: + description: |- + Scopes are a special type of claim in a JWT token that represents the permissions of the client. + + The value of the scopes field should be a space delimited string that is expected in the scope parameter, + as defined in RFC 6749: https://datatracker.ietf.org/doc/html/rfc6749#page-23. + + If multiple scopes are specified, all scopes must match for the rule to match. + items: + maxLength: 253 + minLength: 1 + type: string + maxItems: 16 + minItems: 1 + type: array + required: + - provider + type: object + x-kubernetes-validations: + - message: at least one of claims or scopes must be + specified + rule: (has(self.claims) || has(self.scopes)) + type: object + x-kubernetes-validations: + - message: at least one of clientCIDRs, jwt, or headers + must be specified + rule: (has(self.clientCIDRs) || has(self.jwt) || has(self.headers)) + required: + - action + - principal + type: object + type: array + type: object + basicAuth: + description: BasicAuth defines the configuration for the HTTP Basic + Authentication. + properties: + forwardUsernameHeader: + description: |- + This field specifies the header name to forward a successfully authenticated user to + the backend. The header will be added to the request with the username as the value. + + If it is not specified, the username will not be forwarded. + type: string + users: + description: |- + The Kubernetes secret which contains the username-password pairs in + htpasswd format, used to verify user credentials in the "Authorization" + header. + + This is an Opaque secret. The username-password pairs should be stored in + the key ".htpasswd". As the key name indicates, the value needs to be the + htpasswd format, for example: "user1:{SHA}hashed_user1_password". + Right now, only SHA hash algorithm is supported. + Reference to https://httpd.apache.org/docs/2.4/programs/htpasswd.html + for more details. + + Note: The secret must be in the same namespace as the SecurityPolicy. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + required: + - users + type: object + cors: + description: CORS defines the configuration for Cross-Origin Resource + Sharing (CORS). + properties: + allowCredentials: + description: |- + AllowCredentials indicates whether a request can include user credentials + like cookies, authentication headers, or TLS client certificates. + It specifies the value in the Access-Control-Allow-Credentials CORS response header. + type: boolean + allowHeaders: + description: |- + AllowHeaders defines the headers that are allowed to be sent with requests. + It specifies the allowed headers in the Access-Control-Allow-Headers CORS response header.. + The value "*" allows any header to be sent. + items: + type: string + type: array + allowMethods: + description: |- + AllowMethods defines the methods that are allowed to make requests. + It specifies the allowed methods in the Access-Control-Allow-Methods CORS response header.. + The value "*" allows any method to be used. + items: + type: string + type: array + allowOrigins: + description: |- + AllowOrigins defines the origins that are allowed to make requests. + It specifies the allowed origins in the Access-Control-Allow-Origin CORS response header. + The value "*" allows any origin to make requests. + items: + description: |- + Origin is defined by the scheme (protocol), hostname (domain), and port of + the URL used to access it. The hostname can be "precise" which is just the + domain name or "wildcard" which is a domain name prefixed with a single + wildcard label such as "*.example.com". + In addition to that a single wildcard (with or without scheme) can be + configured to match any origin. + + For example, the following are valid origins: + - https://foo.example.com + - https://*.example.com + - http://foo.example.com:8080 + - http://*.example.com:8080 + - https://* + maxLength: 253 + minLength: 1 + pattern: ^(\*|https?:\/\/(\*|(\*\.)?(([\w-]+\.?)+)?[\w-]+)(:\d{1,5})?)$ + type: string + type: array + exposeHeaders: + description: |- + ExposeHeaders defines which response headers should be made accessible to + scripts running in the browser. + It specifies the headers in the Access-Control-Expose-Headers CORS response header.. + The value "*" allows any header to be exposed. + items: + type: string + type: array + maxAge: + description: |- + MaxAge defines how long the results of a preflight request can be cached. + It specifies the value in the Access-Control-Max-Age CORS response header.. + type: string + type: object + extAuth: + description: ExtAuth defines the configuration for External Authorization. + properties: + bodyToExtAuth: + description: BodyToExtAuth defines the Body to Ext Auth configuration. + properties: + maxRequestBytes: + description: |- + MaxRequestBytes is the maximum size of a message body that the filter will hold in memory. + Envoy will return HTTP 413 and will not initiate the authorization process when buffer + reaches the number set in this field. + Note that this setting will have precedence over failOpen mode. + format: int32 + minimum: 1 + type: integer + required: + - maxRequestBytes + type: object + failOpen: + default: false + description: |- + FailOpen is a switch used to control the behavior when a response from the External Authorization service cannot be obtained. + If FailOpen is set to true, the system allows the traffic to pass through. + Otherwise, if it is set to false or not set (defaulting to false), + the system blocks the traffic and returns a HTTP 5xx error, reflecting a fail-closed approach. + This setting determines whether to prioritize accessibility over strict security in case of authorization service failure. + type: boolean + grpc: + description: |- + GRPC defines the gRPC External Authorization service. + Either GRPCService or HTTPService must be specified, + and only one of them can be provided. + properties: + backendRef: + description: |- + BackendRef references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + + Deprecated: Use BackendRefs instead. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + backendRefs: + description: |- + BackendRefs references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + items: + description: BackendRef defines how an ObjectReference that + is specific to BackendRef. + properties: + fallback: + description: |- + Fallback indicates whether the backend is designated as a fallback. + Multiple fallback backends can be configured. + It is highly recommended to configure active or passive health checks to ensure that failover can be detected + when the active backends become unhealthy and to automatically readjust once the primary backends are healthy again. + The overprovisioning factor is set to 1.4, meaning the fallback backends will only start receiving traffic when + the health of the active backends falls below 72%. + type: boolean + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + backendSettings: + description: |- + BackendSettings holds configuration for managing the connection + to the backend. + properties: + circuitBreaker: + description: |- + Circuit Breaker settings for the upstream connections and requests. + If not set, circuit breakers will be enabled with the default thresholds + properties: + maxConnections: + default: 1024 + description: The maximum number of connections that + Envoy will establish to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRequests: + default: 1024 + description: The maximum number of parallel requests + that Envoy will make to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRetries: + default: 1024 + description: The maximum number of parallel retries + that Envoy will make to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxPendingRequests: + default: 1024 + description: The maximum number of pending requests + that Envoy will queue to the referenced backend + defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxRequestsPerConnection: + description: |- + The maximum number of requests that Envoy will make over a single connection to the referenced backend defined within a xRoute rule. + Default: unlimited. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + perEndpoint: + description: PerEndpoint defines Circuit Breakers + that will apply per-endpoint for an upstream cluster + properties: + maxConnections: + default: 1024 + description: MaxConnections configures the maximum + number of connections that Envoy will establish + per-endpoint to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + type: object + type: object + connection: + description: Connection includes backend connection settings. + properties: + bufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + BufferLimit Soft limit on size of the cluster’s connections read and write buffers. + BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space. + If unspecified, an implementation defined default is applied (32768 bytes). + For example, 20Mi, 1Gi, 256Ki etc. + Note: that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + socketBufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + SocketBufferLimit provides configuration for the maximum buffer size in bytes for each socket + to backend. + SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space. + For example, 20Mi, 1Gi, 256Ki etc. + Note that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + type: object + dns: + description: DNS includes dns resolution settings. + properties: + dnsRefreshRate: + description: |- + DNSRefreshRate specifies the rate at which DNS records should be refreshed. + Defaults to 30 seconds. + type: string + lookupFamily: + description: |- + LookupFamily determines how Envoy would resolve DNS for Routes where the backend is specified as a fully qualified domain name (FQDN). + If set, this configuration overrides other defaults. + enum: + - IPv4 + - IPv6 + - IPv4Preferred + - IPv6Preferred + - IPv4AndIPv6 + type: string + respectDnsTtl: + description: |- + RespectDNSTTL indicates whether the DNS Time-To-Live (TTL) should be respected. + If the value is set to true, the DNS refresh rate will be set to the resource record’s TTL. + Defaults to true. + type: boolean + type: object + healthCheck: + description: HealthCheck allows gateway to perform active + health checking on backends. + properties: + active: + description: Active health check configuration + properties: + grpc: + description: |- + GRPC defines the configuration of the GRPC health checker. + It's optional, and can only be used if the specified type is GRPC. + properties: + service: + description: |- + Service to send in the health check request. + If this is not specified, then the health check request applies to the entire + server and not to a specific service. + type: string + type: object + healthyThreshold: + default: 1 + description: HealthyThreshold defines the number + of healthy health checks required before a backend + host is marked healthy. + format: int32 + minimum: 1 + type: integer + http: + description: |- + HTTP defines the configuration of http health checker. + It's required while the health checker type is HTTP. + properties: + expectedResponse: + description: ExpectedResponse defines a list + of HTTP expected responses to match. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of + the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, binary + field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + expectedStatuses: + description: |- + ExpectedStatuses defines a list of HTTP response statuses considered healthy. + Defaults to 200 only + items: + description: HTTPStatus defines the http + status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + method: + description: |- + Method defines the HTTP method used for health checking. + Defaults to GET + type: string + path: + description: Path defines the HTTP path that + will be requested during health checking. + maxLength: 1024 + minLength: 1 + type: string + required: + - path + type: object + interval: + default: 3s + description: Interval defines the time between + active health checks. + format: duration + type: string + tcp: + description: |- + TCP defines the configuration of tcp health checker. + It's required while the health checker type is TCP. + properties: + receive: + description: Receive defines the expected + response payload. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of + the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, binary + field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + send: + description: Send defines the request payload. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of + the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, binary + field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + type: object + timeout: + default: 1s + description: Timeout defines the time to wait + for a health check response. + format: duration + type: string + type: + allOf: + - enum: + - HTTP + - TCP + - GRPC + - enum: + - HTTP + - TCP + - GRPC + description: Type defines the type of health checker. + type: string + unhealthyThreshold: + default: 3 + description: UnhealthyThreshold defines the number + of unhealthy health checks required before a + backend host is marked unhealthy. + format: int32 + minimum: 1 + type: integer + required: + - type + type: object + x-kubernetes-validations: + - message: If Health Checker type is HTTP, http field + needs to be set. + rule: 'self.type == ''HTTP'' ? has(self.http) : + !has(self.http)' + - message: If Health Checker type is TCP, tcp field + needs to be set. + rule: 'self.type == ''TCP'' ? has(self.tcp) : !has(self.tcp)' + - message: The grpc field can only be set if the Health + Checker type is GRPC. + rule: 'has(self.grpc) ? self.type == ''GRPC'' : + true' + panicThreshold: + description: |- + When number of unhealthy endpoints for a backend reaches this threshold + Envoy will disregard health status and balance across all endpoints. + It's designed to prevent a situation in which host failures cascade throughout the cluster + as load increases. If not set, the default value is 50%. To disable panic mode, set value to `0`. + format: int32 + maximum: 100 + minimum: 0 + type: integer + passive: + description: Passive passive check configuration + properties: + baseEjectionTime: + default: 30s + description: BaseEjectionTime defines the base + duration for which a host will be ejected on + consecutive failures. + format: duration + type: string + consecutive5XxErrors: + default: 5 + description: Consecutive5xxErrors sets the number + of consecutive 5xx errors triggering ejection. + format: int32 + type: integer + consecutiveGatewayErrors: + default: 0 + description: ConsecutiveGatewayErrors sets the + number of consecutive gateway errors triggering + ejection. + format: int32 + type: integer + consecutiveLocalOriginFailures: + default: 5 + description: |- + ConsecutiveLocalOriginFailures sets the number of consecutive local origin failures triggering ejection. + Parameter takes effect only when split_external_local_origin_errors is set to true. + format: int32 + type: integer + interval: + default: 3s + description: Interval defines the time between + passive health checks. + format: duration + type: string + maxEjectionPercent: + default: 10 + description: MaxEjectionPercent sets the maximum + percentage of hosts in a cluster that can be + ejected. + format: int32 + type: integer + splitExternalLocalOriginErrors: + default: false + description: SplitExternalLocalOriginErrors enables + splitting of errors between external and local + origin. + type: boolean + type: object + type: object + http2: + description: HTTP2 provides HTTP/2 configuration for backend + connections. + properties: + initialConnectionWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialConnectionWindowSize sets the initial window size for HTTP/2 connections. + If not set, the default value is 1 MiB. + x-kubernetes-int-or-string: true + initialStreamWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialStreamWindowSize sets the initial window size for HTTP/2 streams. + If not set, the default value is 64 KiB(64*1024). + x-kubernetes-int-or-string: true + maxConcurrentStreams: + description: |- + MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection. + If not set, the default value is 100. + format: int32 + maximum: 2147483647 + minimum: 1 + type: integer + onInvalidMessage: + description: |- + OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error + It's recommended for L2 Envoy deployments to set this value to TerminateStream. + https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two + Default: TerminateConnection + type: string + type: object + loadBalancer: + description: |- + LoadBalancer policy to apply when routing traffic from the gateway to + the backend endpoints. Defaults to `LeastRequest`. + properties: + consistentHash: + description: |- + ConsistentHash defines the configuration when the load balancer type is + set to ConsistentHash + properties: + cookie: + description: Cookie configures the cookie hash + policy when the consistent hash type is set + to Cookie. + properties: + attributes: + additionalProperties: + type: string + description: Additional Attributes to set + for the generated cookie. + type: object + name: + description: |- + Name of the cookie to hash. + If this cookie does not exist in the request, Envoy will generate a cookie and set + the TTL on the response back to the client based on Layer 4 + attributes of the backend endpoint, to ensure that these future requests + go to the same backend endpoint. Make sure to set the TTL field for this case. + type: string + ttl: + description: |- + TTL of the generated cookie if the cookie is not present. This value sets the + Max-Age attribute value. + type: string + required: + - name + type: object + header: + description: Header configures the header hash + policy when the consistent hash type is set + to Header. + properties: + name: + description: Name of the header to hash. + type: string + required: + - name + type: object + tableSize: + default: 65537 + description: The table size for consistent hashing, + must be prime number limited to 5000011. + format: int64 + maximum: 5000011 + minimum: 2 + type: integer + type: + description: |- + ConsistentHashType defines the type of input to hash on. Valid Type values are + "SourceIP", + "Header", + "Cookie". + enum: + - SourceIP + - Header + - Cookie + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If consistent hash type is header, the + header field must be set. + rule: 'self.type == ''Header'' ? has(self.header) + : !has(self.header)' + - message: If consistent hash type is cookie, the + cookie field must be set. + rule: 'self.type == ''Cookie'' ? has(self.cookie) + : !has(self.cookie)' + slowStart: + description: |- + SlowStart defines the configuration related to the slow start load balancer policy. + If set, during slow start window, traffic sent to the newly added hosts will gradually increase. + Currently this is only supported for RoundRobin and LeastRequest load balancers + properties: + window: + description: |- + Window defines the duration of the warm up period for newly added host. + During slow start window, traffic sent to the newly added hosts will gradually increase. + Currently only supports linear growth of traffic. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#config-cluster-v3-cluster-slowstartconfig + type: string + required: + - window + type: object + type: + description: |- + Type decides the type of Load Balancer policy. + Valid LoadBalancerType values are + "ConsistentHash", + "LeastRequest", + "Random", + "RoundRobin". + enum: + - ConsistentHash + - LeastRequest + - Random + - RoundRobin + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If LoadBalancer type is consistentHash, consistentHash + field needs to be set. + rule: 'self.type == ''ConsistentHash'' ? has(self.consistentHash) + : !has(self.consistentHash)' + - message: Currently SlowStart is only supported for RoundRobin + and LeastRequest load balancers. + rule: 'self.type in [''Random'', ''ConsistentHash''] + ? !has(self.slowStart) : true ' + proxyProtocol: + description: ProxyProtocol enables the Proxy Protocol + when communicating with the backend. + properties: + version: + description: |- + Version of ProxyProtol + Valid ProxyProtocolVersion values are + "V1" + "V2" + enum: + - V1 + - V2 + type: string + required: + - version + type: object + retry: + description: |- + Retry provides more advanced usage, allowing users to customize the number of retries, retry fallback strategy, and retry triggering conditions. + If not set, retry will be disabled. + properties: + numRetries: + default: 2 + description: NumRetries is the number of retries to + be attempted. Defaults to 2. + format: int32 + minimum: 0 + type: integer + perRetry: + description: PerRetry is the retry policy to be applied + per retry attempt. + properties: + backOff: + description: |- + Backoff is the backoff policy to be applied per retry attempt. gateway uses a fully jittered exponential + back-off algorithm for retries. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#config-http-filters-router-x-envoy-max-retries + properties: + baseInterval: + description: BaseInterval is the base interval + between retries. + format: duration + type: string + maxInterval: + description: |- + MaxInterval is the maximum interval between retries. This parameter is optional, but must be greater than or equal to the base_interval if set. + The default is 10 times the base_interval + format: duration + type: string + type: object + timeout: + description: Timeout is the timeout per retry + attempt. + format: duration + type: string + type: object + retryOn: + description: |- + RetryOn specifies the retry trigger condition. + + If not specified, the default is to retry on connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes(503). + properties: + httpStatusCodes: + description: |- + HttpStatusCodes specifies the http status codes to be retried. + The retriable-status-codes trigger must also be configured for these status codes to trigger a retry. + items: + description: HTTPStatus defines the http status + code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + triggers: + description: Triggers specifies the retry trigger + condition(Http/Grpc). + items: + description: TriggerEnum specifies the conditions + that trigger retries. + enum: + - 5xx + - gateway-error + - reset + - connect-failure + - retriable-4xx + - refused-stream + - retriable-status-codes + - cancelled + - deadline-exceeded + - internal + - resource-exhausted + - unavailable + type: string + type: array + type: object + type: object + tcpKeepalive: + description: |- + TcpKeepalive settings associated with the upstream client connection. + Disabled by default. + properties: + idleTime: + description: |- + The duration a connection needs to be idle before keep-alive + probes start being sent. + The duration format is + Defaults to `7200s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + The duration between keep-alive probes. + Defaults to `75s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + probes: + description: |- + The total number of unacknowledged probes to send before deciding + the connection is dead. + Defaults to 9. + format: int32 + type: integer + type: object + timeout: + description: Timeout settings for the backend connections. + properties: + http: + description: Timeout settings for HTTP. + properties: + connectionIdleTimeout: + description: |- + The idle timeout for an HTTP connection. Idle time is defined as a period in which there are no active requests in the connection. + Default: 1 hour. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + maxConnectionDuration: + description: |- + The maximum duration of an HTTP connection. + Default: unlimited. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + requestTimeout: + description: RequestTimeout is the time until + which entire response is received from the upstream. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + tcp: + description: Timeout settings for TCP. + properties: + connectTimeout: + description: |- + The timeout for network connection establishment, including TCP and TLS handshakes. + Default: 10 seconds. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + type: object + type: object + type: object + x-kubernetes-validations: + - message: backendRef or backendRefs needs to be set + rule: has(self.backendRef) || self.backendRefs.size() > 0 + - message: BackendRefs only supports Service and Backend kind. + rule: 'has(self.backendRefs) ? self.backendRefs.all(f, f.kind + == ''Service'' || f.kind == ''Backend'') : true' + - message: BackendRefs only supports Core and gateway.envoyproxy.io + group. + rule: 'has(self.backendRefs) ? (self.backendRefs.all(f, f.group + == "" || f.group == ''gateway.envoyproxy.io'')) : true' + headersToExtAuth: + description: |- + HeadersToExtAuth defines the client request headers that will be included + in the request to the external authorization service. + Note: If not specified, the default behavior for gRPC and HTTP external + authorization services is different due to backward compatibility reasons. + All headers will be included in the check request to a gRPC authorization server. + Only the following headers will be included in the check request to an HTTP + authorization server: Host, Method, Path, Content-Length, and Authorization. + And these headers will always be included to the check request to an HTTP + authorization server by default, no matter whether they are specified + in HeadersToExtAuth or not. + items: + type: string + type: array + http: + description: |- + HTTP defines the HTTP External Authorization service. + Either GRPCService or HTTPService must be specified, + and only one of them can be provided. + properties: + backendRef: + description: |- + BackendRef references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + + Deprecated: Use BackendRefs instead. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + backendRefs: + description: |- + BackendRefs references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + items: + description: BackendRef defines how an ObjectReference that + is specific to BackendRef. + properties: + fallback: + description: |- + Fallback indicates whether the backend is designated as a fallback. + Multiple fallback backends can be configured. + It is highly recommended to configure active or passive health checks to ensure that failover can be detected + when the active backends become unhealthy and to automatically readjust once the primary backends are healthy again. + The overprovisioning factor is set to 1.4, meaning the fallback backends will only start receiving traffic when + the health of the active backends falls below 72%. + type: boolean + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + backendSettings: + description: |- + BackendSettings holds configuration for managing the connection + to the backend. + properties: + circuitBreaker: + description: |- + Circuit Breaker settings for the upstream connections and requests. + If not set, circuit breakers will be enabled with the default thresholds + properties: + maxConnections: + default: 1024 + description: The maximum number of connections that + Envoy will establish to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRequests: + default: 1024 + description: The maximum number of parallel requests + that Envoy will make to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRetries: + default: 1024 + description: The maximum number of parallel retries + that Envoy will make to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxPendingRequests: + default: 1024 + description: The maximum number of pending requests + that Envoy will queue to the referenced backend + defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxRequestsPerConnection: + description: |- + The maximum number of requests that Envoy will make over a single connection to the referenced backend defined within a xRoute rule. + Default: unlimited. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + perEndpoint: + description: PerEndpoint defines Circuit Breakers + that will apply per-endpoint for an upstream cluster + properties: + maxConnections: + default: 1024 + description: MaxConnections configures the maximum + number of connections that Envoy will establish + per-endpoint to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + type: object + type: object + connection: + description: Connection includes backend connection settings. + properties: + bufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + BufferLimit Soft limit on size of the cluster’s connections read and write buffers. + BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space. + If unspecified, an implementation defined default is applied (32768 bytes). + For example, 20Mi, 1Gi, 256Ki etc. + Note: that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + socketBufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + SocketBufferLimit provides configuration for the maximum buffer size in bytes for each socket + to backend. + SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space. + For example, 20Mi, 1Gi, 256Ki etc. + Note that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + type: object + dns: + description: DNS includes dns resolution settings. + properties: + dnsRefreshRate: + description: |- + DNSRefreshRate specifies the rate at which DNS records should be refreshed. + Defaults to 30 seconds. + type: string + lookupFamily: + description: |- + LookupFamily determines how Envoy would resolve DNS for Routes where the backend is specified as a fully qualified domain name (FQDN). + If set, this configuration overrides other defaults. + enum: + - IPv4 + - IPv6 + - IPv4Preferred + - IPv6Preferred + - IPv4AndIPv6 + type: string + respectDnsTtl: + description: |- + RespectDNSTTL indicates whether the DNS Time-To-Live (TTL) should be respected. + If the value is set to true, the DNS refresh rate will be set to the resource record’s TTL. + Defaults to true. + type: boolean + type: object + healthCheck: + description: HealthCheck allows gateway to perform active + health checking on backends. + properties: + active: + description: Active health check configuration + properties: + grpc: + description: |- + GRPC defines the configuration of the GRPC health checker. + It's optional, and can only be used if the specified type is GRPC. + properties: + service: + description: |- + Service to send in the health check request. + If this is not specified, then the health check request applies to the entire + server and not to a specific service. + type: string + type: object + healthyThreshold: + default: 1 + description: HealthyThreshold defines the number + of healthy health checks required before a backend + host is marked healthy. + format: int32 + minimum: 1 + type: integer + http: + description: |- + HTTP defines the configuration of http health checker. + It's required while the health checker type is HTTP. + properties: + expectedResponse: + description: ExpectedResponse defines a list + of HTTP expected responses to match. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of + the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, binary + field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + expectedStatuses: + description: |- + ExpectedStatuses defines a list of HTTP response statuses considered healthy. + Defaults to 200 only + items: + description: HTTPStatus defines the http + status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + method: + description: |- + Method defines the HTTP method used for health checking. + Defaults to GET + type: string + path: + description: Path defines the HTTP path that + will be requested during health checking. + maxLength: 1024 + minLength: 1 + type: string + required: + - path + type: object + interval: + default: 3s + description: Interval defines the time between + active health checks. + format: duration + type: string + tcp: + description: |- + TCP defines the configuration of tcp health checker. + It's required while the health checker type is TCP. + properties: + receive: + description: Receive defines the expected + response payload. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of + the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, binary + field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + send: + description: Send defines the request payload. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of + the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, binary + field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + type: object + timeout: + default: 1s + description: Timeout defines the time to wait + for a health check response. + format: duration + type: string + type: + allOf: + - enum: + - HTTP + - TCP + - GRPC + - enum: + - HTTP + - TCP + - GRPC + description: Type defines the type of health checker. + type: string + unhealthyThreshold: + default: 3 + description: UnhealthyThreshold defines the number + of unhealthy health checks required before a + backend host is marked unhealthy. + format: int32 + minimum: 1 + type: integer + required: + - type + type: object + x-kubernetes-validations: + - message: If Health Checker type is HTTP, http field + needs to be set. + rule: 'self.type == ''HTTP'' ? has(self.http) : + !has(self.http)' + - message: If Health Checker type is TCP, tcp field + needs to be set. + rule: 'self.type == ''TCP'' ? has(self.tcp) : !has(self.tcp)' + - message: The grpc field can only be set if the Health + Checker type is GRPC. + rule: 'has(self.grpc) ? self.type == ''GRPC'' : + true' + panicThreshold: + description: |- + When number of unhealthy endpoints for a backend reaches this threshold + Envoy will disregard health status and balance across all endpoints. + It's designed to prevent a situation in which host failures cascade throughout the cluster + as load increases. If not set, the default value is 50%. To disable panic mode, set value to `0`. + format: int32 + maximum: 100 + minimum: 0 + type: integer + passive: + description: Passive passive check configuration + properties: + baseEjectionTime: + default: 30s + description: BaseEjectionTime defines the base + duration for which a host will be ejected on + consecutive failures. + format: duration + type: string + consecutive5XxErrors: + default: 5 + description: Consecutive5xxErrors sets the number + of consecutive 5xx errors triggering ejection. + format: int32 + type: integer + consecutiveGatewayErrors: + default: 0 + description: ConsecutiveGatewayErrors sets the + number of consecutive gateway errors triggering + ejection. + format: int32 + type: integer + consecutiveLocalOriginFailures: + default: 5 + description: |- + ConsecutiveLocalOriginFailures sets the number of consecutive local origin failures triggering ejection. + Parameter takes effect only when split_external_local_origin_errors is set to true. + format: int32 + type: integer + interval: + default: 3s + description: Interval defines the time between + passive health checks. + format: duration + type: string + maxEjectionPercent: + default: 10 + description: MaxEjectionPercent sets the maximum + percentage of hosts in a cluster that can be + ejected. + format: int32 + type: integer + splitExternalLocalOriginErrors: + default: false + description: SplitExternalLocalOriginErrors enables + splitting of errors between external and local + origin. + type: boolean + type: object + type: object + http2: + description: HTTP2 provides HTTP/2 configuration for backend + connections. + properties: + initialConnectionWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialConnectionWindowSize sets the initial window size for HTTP/2 connections. + If not set, the default value is 1 MiB. + x-kubernetes-int-or-string: true + initialStreamWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialStreamWindowSize sets the initial window size for HTTP/2 streams. + If not set, the default value is 64 KiB(64*1024). + x-kubernetes-int-or-string: true + maxConcurrentStreams: + description: |- + MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection. + If not set, the default value is 100. + format: int32 + maximum: 2147483647 + minimum: 1 + type: integer + onInvalidMessage: + description: |- + OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error + It's recommended for L2 Envoy deployments to set this value to TerminateStream. + https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two + Default: TerminateConnection + type: string + type: object + loadBalancer: + description: |- + LoadBalancer policy to apply when routing traffic from the gateway to + the backend endpoints. Defaults to `LeastRequest`. + properties: + consistentHash: + description: |- + ConsistentHash defines the configuration when the load balancer type is + set to ConsistentHash + properties: + cookie: + description: Cookie configures the cookie hash + policy when the consistent hash type is set + to Cookie. + properties: + attributes: + additionalProperties: + type: string + description: Additional Attributes to set + for the generated cookie. + type: object + name: + description: |- + Name of the cookie to hash. + If this cookie does not exist in the request, Envoy will generate a cookie and set + the TTL on the response back to the client based on Layer 4 + attributes of the backend endpoint, to ensure that these future requests + go to the same backend endpoint. Make sure to set the TTL field for this case. + type: string + ttl: + description: |- + TTL of the generated cookie if the cookie is not present. This value sets the + Max-Age attribute value. + type: string + required: + - name + type: object + header: + description: Header configures the header hash + policy when the consistent hash type is set + to Header. + properties: + name: + description: Name of the header to hash. + type: string + required: + - name + type: object + tableSize: + default: 65537 + description: The table size for consistent hashing, + must be prime number limited to 5000011. + format: int64 + maximum: 5000011 + minimum: 2 + type: integer + type: + description: |- + ConsistentHashType defines the type of input to hash on. Valid Type values are + "SourceIP", + "Header", + "Cookie". + enum: + - SourceIP + - Header + - Cookie + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If consistent hash type is header, the + header field must be set. + rule: 'self.type == ''Header'' ? has(self.header) + : !has(self.header)' + - message: If consistent hash type is cookie, the + cookie field must be set. + rule: 'self.type == ''Cookie'' ? has(self.cookie) + : !has(self.cookie)' + slowStart: + description: |- + SlowStart defines the configuration related to the slow start load balancer policy. + If set, during slow start window, traffic sent to the newly added hosts will gradually increase. + Currently this is only supported for RoundRobin and LeastRequest load balancers + properties: + window: + description: |- + Window defines the duration of the warm up period for newly added host. + During slow start window, traffic sent to the newly added hosts will gradually increase. + Currently only supports linear growth of traffic. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#config-cluster-v3-cluster-slowstartconfig + type: string + required: + - window + type: object + type: + description: |- + Type decides the type of Load Balancer policy. + Valid LoadBalancerType values are + "ConsistentHash", + "LeastRequest", + "Random", + "RoundRobin". + enum: + - ConsistentHash + - LeastRequest + - Random + - RoundRobin + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If LoadBalancer type is consistentHash, consistentHash + field needs to be set. + rule: 'self.type == ''ConsistentHash'' ? has(self.consistentHash) + : !has(self.consistentHash)' + - message: Currently SlowStart is only supported for RoundRobin + and LeastRequest load balancers. + rule: 'self.type in [''Random'', ''ConsistentHash''] + ? !has(self.slowStart) : true ' + proxyProtocol: + description: ProxyProtocol enables the Proxy Protocol + when communicating with the backend. + properties: + version: + description: |- + Version of ProxyProtol + Valid ProxyProtocolVersion values are + "V1" + "V2" + enum: + - V1 + - V2 + type: string + required: + - version + type: object + retry: + description: |- + Retry provides more advanced usage, allowing users to customize the number of retries, retry fallback strategy, and retry triggering conditions. + If not set, retry will be disabled. + properties: + numRetries: + default: 2 + description: NumRetries is the number of retries to + be attempted. Defaults to 2. + format: int32 + minimum: 0 + type: integer + perRetry: + description: PerRetry is the retry policy to be applied + per retry attempt. + properties: + backOff: + description: |- + Backoff is the backoff policy to be applied per retry attempt. gateway uses a fully jittered exponential + back-off algorithm for retries. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#config-http-filters-router-x-envoy-max-retries + properties: + baseInterval: + description: BaseInterval is the base interval + between retries. + format: duration + type: string + maxInterval: + description: |- + MaxInterval is the maximum interval between retries. This parameter is optional, but must be greater than or equal to the base_interval if set. + The default is 10 times the base_interval + format: duration + type: string + type: object + timeout: + description: Timeout is the timeout per retry + attempt. + format: duration + type: string + type: object + retryOn: + description: |- + RetryOn specifies the retry trigger condition. + + If not specified, the default is to retry on connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes(503). + properties: + httpStatusCodes: + description: |- + HttpStatusCodes specifies the http status codes to be retried. + The retriable-status-codes trigger must also be configured for these status codes to trigger a retry. + items: + description: HTTPStatus defines the http status + code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + triggers: + description: Triggers specifies the retry trigger + condition(Http/Grpc). + items: + description: TriggerEnum specifies the conditions + that trigger retries. + enum: + - 5xx + - gateway-error + - reset + - connect-failure + - retriable-4xx + - refused-stream + - retriable-status-codes + - cancelled + - deadline-exceeded + - internal + - resource-exhausted + - unavailable + type: string + type: array + type: object + type: object + tcpKeepalive: + description: |- + TcpKeepalive settings associated with the upstream client connection. + Disabled by default. + properties: + idleTime: + description: |- + The duration a connection needs to be idle before keep-alive + probes start being sent. + The duration format is + Defaults to `7200s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + The duration between keep-alive probes. + Defaults to `75s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + probes: + description: |- + The total number of unacknowledged probes to send before deciding + the connection is dead. + Defaults to 9. + format: int32 + type: integer + type: object + timeout: + description: Timeout settings for the backend connections. + properties: + http: + description: Timeout settings for HTTP. + properties: + connectionIdleTimeout: + description: |- + The idle timeout for an HTTP connection. Idle time is defined as a period in which there are no active requests in the connection. + Default: 1 hour. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + maxConnectionDuration: + description: |- + The maximum duration of an HTTP connection. + Default: unlimited. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + requestTimeout: + description: RequestTimeout is the time until + which entire response is received from the upstream. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + tcp: + description: Timeout settings for TCP. + properties: + connectTimeout: + description: |- + The timeout for network connection establishment, including TCP and TLS handshakes. + Default: 10 seconds. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + type: object + type: object + headersToBackend: + description: |- + HeadersToBackend are the authorization response headers that will be added + to the original client request before sending it to the backend server. + Note that coexisting headers will be overridden. + If not specified, no authorization response headers will be added to the + original client request. + items: + type: string + type: array + path: + description: |- + Path is the path of the HTTP External Authorization service. + If path is specified, the authorization request will be sent to that path, + or else the authorization request will use the path of the original request. + + Please note that the original request path will be appended to the path specified here. + For example, if the original request path is "/hello", and the path specified here is "/auth", + then the path of the authorization request will be "/auth/hello". If the path is not specified, + the path of the authorization request will be "/hello". + type: string + type: object + x-kubernetes-validations: + - message: backendRef or backendRefs needs to be set + rule: has(self.backendRef) || self.backendRefs.size() > 0 + - message: BackendRefs only supports Service and Backend kind. + rule: 'has(self.backendRefs) ? self.backendRefs.all(f, f.kind + == ''Service'' || f.kind == ''Backend'') : true' + - message: BackendRefs only supports Core and gateway.envoyproxy.io + group. + rule: 'has(self.backendRefs) ? (self.backendRefs.all(f, f.group + == "" || f.group == ''gateway.envoyproxy.io'')) : true' + recomputeRoute: + description: |- + RecomputeRoute clears the route cache and recalculates the routing decision. + This field must be enabled if the headers added or modified by the ExtAuth are used for + route matching decisions. If the recomputation selects a new route, features targeting + the new matched route will be applied. + type: boolean + type: object + x-kubernetes-validations: + - message: one of grpc or http must be specified + rule: (has(self.grpc) || has(self.http)) + - message: only one of grpc or http can be specified + rule: (has(self.grpc) && !has(self.http)) || (!has(self.grpc) && + has(self.http)) + jwt: + description: JWT defines the configuration for JSON Web Token (JWT) + authentication. + properties: + optional: + description: |- + Optional determines whether a missing JWT is acceptable, defaulting to false if not specified. + Note: Even if optional is set to true, JWT authentication will still fail if an invalid JWT is presented. + type: boolean + providers: + description: |- + Providers defines the JSON Web Token (JWT) authentication provider type. + When multiple JWT providers 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. + items: + description: JWTProvider defines how a JSON Web Token (JWT) + can be verified. + properties: + audiences: + description: |- + 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. + items: + type: string + maxItems: 8 + type: array + claimToHeaders: + description: |- + ClaimToHeaders is a list of JWT claims that must be extracted into HTTP request headers + For examples, following config: + The claim must be of type; string, int, double, bool. Array type claims are not supported + items: + description: ClaimToHeader defines a configuration to + convert JWT claims into HTTP headers + properties: + claim: + description: |- + Claim is the JWT Claim that should be saved into the header : it can be a nested claim of type + (eg. "claim.nested.key", "sub"). The nested claim name must use dot "." + to separate the JSON name path. + type: string + header: + description: Header defines the name of the HTTP request + header that the JWT Claim will be saved into. + type: string + required: + - claim + - header + type: object + type: array + extractFrom: + description: |- + ExtractFrom defines different ways to extract the JWT token from HTTP request. + If empty, it defaults to extract JWT token from the Authorization HTTP request header using Bearer schema + or access_token from query parameters. + properties: + cookies: + description: Cookies represents a list of cookie names + to extract the JWT token from. + items: + type: string + type: array + headers: + description: Headers represents a list of HTTP request + headers to extract the JWT token from. + items: + description: JWTHeaderExtractor defines an HTTP header + location to extract JWT token + properties: + name: + description: Name is the HTTP header name to retrieve + the token + type: string + valuePrefix: + description: |- + ValuePrefix is the prefix that should be stripped before extracting the token. + The format would be used by Envoy like "{ValuePrefix}". + For example, "Authorization: Bearer ", then the ValuePrefix="Bearer " with a space at the end. + type: string + required: + - name + type: object + type: array + params: + description: Params represents a list of query parameters + to extract the JWT token from. + items: + type: string + type: array + type: object + issuer: + description: |- + 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. + maxLength: 253 + type: string + localJWKS: + description: LocalJWKS defines how to get the JSON Web Key + Sets (JWKS) from a local source. + properties: + inline: + description: Inline contains the value as an inline + string. + type: string + type: + default: Inline + description: |- + Type is the type of method to use to read the body value. + Valid values are Inline and ValueRef, default is Inline. + enum: + - Inline + - ValueRef + type: string + valueRef: + description: |- + ValueRef is a reference to a local ConfigMap that contains the JSON Web Key Sets (JWKS). + + The value of key `jwks` in the ConfigMap will be used. + If the key is not found, the first value in the ConfigMap will be used. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: Exactly one of inline or valueRef must be set + with correct type. + rule: (self.type == 'Inline' && has(self.inline) && !has(self.valueRef)) + || (self.type == 'ValueRef' && !has(self.inline) && + has(self.valueRef)) + name: + description: |- + 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. + maxLength: 253 + minLength: 1 + type: string + recomputeRoute: + description: |- + RecomputeRoute clears the route cache and recalculates the routing decision. + This field must be enabled if the headers generated from the claim are used for + route matching decisions. If the recomputation selects a new route, features targeting + the new matched route will be applied. + type: boolean + remoteJWKS: + description: |- + RemoteJWKS defines how to fetch and cache JSON Web Key Sets (JWKS) from a remote + HTTP/HTTPS endpoint. + properties: + backendRef: + description: |- + BackendRef references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + + Deprecated: Use BackendRefs instead. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + backendRefs: + description: |- + BackendRefs references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + items: + description: BackendRef defines how an ObjectReference + that is specific to BackendRef. + properties: + fallback: + description: |- + Fallback indicates whether the backend is designated as a fallback. + Multiple fallback backends can be configured. + It is highly recommended to configure active or passive health checks to ensure that failover can be detected + when the active backends become unhealthy and to automatically readjust once the primary backends are healthy again. + The overprovisioning factor is set to 1.4, meaning the fallback backends will only start receiving traffic when + the health of the active backends falls below 72%. + type: boolean + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + backendSettings: + description: |- + BackendSettings holds configuration for managing the connection + to the backend. + properties: + circuitBreaker: + description: |- + Circuit Breaker settings for the upstream connections and requests. + If not set, circuit breakers will be enabled with the default thresholds + properties: + maxConnections: + default: 1024 + description: The maximum number of connections + that Envoy will establish to the referenced + backend defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRequests: + default: 1024 + description: The maximum number of parallel + requests that Envoy will make to the referenced + backend defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRetries: + default: 1024 + description: The maximum number of parallel + retries that Envoy will make to the referenced + backend defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxPendingRequests: + default: 1024 + description: The maximum number of pending requests + that Envoy will queue to the referenced backend + defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxRequestsPerConnection: + description: |- + The maximum number of requests that Envoy will make over a single connection to the referenced backend defined within a xRoute rule. + Default: unlimited. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + perEndpoint: + description: PerEndpoint defines Circuit Breakers + that will apply per-endpoint for an upstream + cluster + properties: + maxConnections: + default: 1024 + description: MaxConnections configures the + maximum number of connections that Envoy + will establish per-endpoint to the referenced + backend defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + type: object + type: object + connection: + description: Connection includes backend connection + settings. + properties: + bufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + BufferLimit Soft limit on size of the cluster’s connections read and write buffers. + BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space. + If unspecified, an implementation defined default is applied (32768 bytes). + For example, 20Mi, 1Gi, 256Ki etc. + Note: that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + socketBufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + SocketBufferLimit provides configuration for the maximum buffer size in bytes for each socket + to backend. + SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space. + For example, 20Mi, 1Gi, 256Ki etc. + Note that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + type: object + dns: + description: DNS includes dns resolution settings. + properties: + dnsRefreshRate: + description: |- + DNSRefreshRate specifies the rate at which DNS records should be refreshed. + Defaults to 30 seconds. + type: string + lookupFamily: + description: |- + LookupFamily determines how Envoy would resolve DNS for Routes where the backend is specified as a fully qualified domain name (FQDN). + If set, this configuration overrides other defaults. + enum: + - IPv4 + - IPv6 + - IPv4Preferred + - IPv6Preferred + - IPv4AndIPv6 + type: string + respectDnsTtl: + description: |- + RespectDNSTTL indicates whether the DNS Time-To-Live (TTL) should be respected. + If the value is set to true, the DNS refresh rate will be set to the resource record’s TTL. + Defaults to true. + type: boolean + type: object + healthCheck: + description: HealthCheck allows gateway to perform + active health checking on backends. + properties: + active: + description: Active health check configuration + properties: + grpc: + description: |- + GRPC defines the configuration of the GRPC health checker. + It's optional, and can only be used if the specified type is GRPC. + properties: + service: + description: |- + Service to send in the health check request. + If this is not specified, then the health check request applies to the entire + server and not to a specific service. + type: string + type: object + healthyThreshold: + default: 1 + description: HealthyThreshold defines the + number of healthy health checks required + before a backend host is marked healthy. + format: int32 + minimum: 1 + type: integer + http: + description: |- + HTTP defines the configuration of http health checker. + It's required while the health checker type is HTTP. + properties: + expectedResponse: + description: ExpectedResponse defines + a list of HTTP expected responses + to match. + properties: + binary: + description: Binary payload base64 + encoded. + format: byte + type: string + text: + description: Text payload in plain + text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type + of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, + text field needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, + binary field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + expectedStatuses: + description: |- + ExpectedStatuses defines a list of HTTP response statuses considered healthy. + Defaults to 200 only + items: + description: HTTPStatus defines the + http status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + method: + description: |- + Method defines the HTTP method used for health checking. + Defaults to GET + type: string + path: + description: Path defines the HTTP path + that will be requested during health + checking. + maxLength: 1024 + minLength: 1 + type: string + required: + - path + type: object + interval: + default: 3s + description: Interval defines the time between + active health checks. + format: duration + type: string + tcp: + description: |- + TCP defines the configuration of tcp health checker. + It's required while the health checker type is TCP. + properties: + receive: + description: Receive defines the expected + response payload. + properties: + binary: + description: Binary payload base64 + encoded. + format: byte + type: string + text: + description: Text payload in plain + text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type + of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, + text field needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, + binary field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + send: + description: Send defines the request + payload. + properties: + binary: + description: Binary payload base64 + encoded. + format: byte + type: string + text: + description: Text payload in plain + text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type + of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, + text field needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, + binary field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + type: object + timeout: + default: 1s + description: Timeout defines the time to + wait for a health check response. + format: duration + type: string + type: + allOf: + - enum: + - HTTP + - TCP + - GRPC + - enum: + - HTTP + - TCP + - GRPC + description: Type defines the type of health + checker. + type: string + unhealthyThreshold: + default: 3 + description: UnhealthyThreshold defines + the number of unhealthy health checks + required before a backend host is marked + unhealthy. + format: int32 + minimum: 1 + type: integer + required: + - type + type: object + x-kubernetes-validations: + - message: If Health Checker type is HTTP, http + field needs to be set. + rule: 'self.type == ''HTTP'' ? has(self.http) + : !has(self.http)' + - message: If Health Checker type is TCP, tcp + field needs to be set. + rule: 'self.type == ''TCP'' ? has(self.tcp) + : !has(self.tcp)' + - message: The grpc field can only be set if + the Health Checker type is GRPC. + rule: 'has(self.grpc) ? self.type == ''GRPC'' + : true' + panicThreshold: + description: |- + When number of unhealthy endpoints for a backend reaches this threshold + Envoy will disregard health status and balance across all endpoints. + It's designed to prevent a situation in which host failures cascade throughout the cluster + as load increases. If not set, the default value is 50%. To disable panic mode, set value to `0`. + format: int32 + maximum: 100 + minimum: 0 + type: integer + passive: + description: Passive passive check configuration + properties: + baseEjectionTime: + default: 30s + description: BaseEjectionTime defines the + base duration for which a host will be + ejected on consecutive failures. + format: duration + type: string + consecutive5XxErrors: + default: 5 + description: Consecutive5xxErrors sets the + number of consecutive 5xx errors triggering + ejection. + format: int32 + type: integer + consecutiveGatewayErrors: + default: 0 + description: ConsecutiveGatewayErrors sets + the number of consecutive gateway errors + triggering ejection. + format: int32 + type: integer + consecutiveLocalOriginFailures: + default: 5 + description: |- + ConsecutiveLocalOriginFailures sets the number of consecutive local origin failures triggering ejection. + Parameter takes effect only when split_external_local_origin_errors is set to true. + format: int32 + type: integer + interval: + default: 3s + description: Interval defines the time between + passive health checks. + format: duration + type: string + maxEjectionPercent: + default: 10 + description: MaxEjectionPercent sets the + maximum percentage of hosts in a cluster + that can be ejected. + format: int32 + type: integer + splitExternalLocalOriginErrors: + default: false + description: SplitExternalLocalOriginErrors + enables splitting of errors between external + and local origin. + type: boolean + type: object + type: object + http2: + description: HTTP2 provides HTTP/2 configuration + for backend connections. + properties: + initialConnectionWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialConnectionWindowSize sets the initial window size for HTTP/2 connections. + If not set, the default value is 1 MiB. + x-kubernetes-int-or-string: true + initialStreamWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialStreamWindowSize sets the initial window size for HTTP/2 streams. + If not set, the default value is 64 KiB(64*1024). + x-kubernetes-int-or-string: true + maxConcurrentStreams: + description: |- + MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection. + If not set, the default value is 100. + format: int32 + maximum: 2147483647 + minimum: 1 + type: integer + onInvalidMessage: + description: |- + OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error + It's recommended for L2 Envoy deployments to set this value to TerminateStream. + https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two + Default: TerminateConnection + type: string + type: object + loadBalancer: + description: |- + LoadBalancer policy to apply when routing traffic from the gateway to + the backend endpoints. Defaults to `LeastRequest`. + properties: + consistentHash: + description: |- + ConsistentHash defines the configuration when the load balancer type is + set to ConsistentHash + properties: + cookie: + description: Cookie configures the cookie + hash policy when the consistent hash type + is set to Cookie. + properties: + attributes: + additionalProperties: + type: string + description: Additional Attributes to + set for the generated cookie. + type: object + name: + description: |- + Name of the cookie to hash. + If this cookie does not exist in the request, Envoy will generate a cookie and set + the TTL on the response back to the client based on Layer 4 + attributes of the backend endpoint, to ensure that these future requests + go to the same backend endpoint. Make sure to set the TTL field for this case. + type: string + ttl: + description: |- + TTL of the generated cookie if the cookie is not present. This value sets the + Max-Age attribute value. + type: string + required: + - name + type: object + header: + description: Header configures the header + hash policy when the consistent hash type + is set to Header. + properties: + name: + description: Name of the header to hash. + type: string + required: + - name + type: object + tableSize: + default: 65537 + description: The table size for consistent + hashing, must be prime number limited + to 5000011. + format: int64 + maximum: 5000011 + minimum: 2 + type: integer + type: + description: |- + ConsistentHashType defines the type of input to hash on. Valid Type values are + "SourceIP", + "Header", + "Cookie". + enum: + - SourceIP + - Header + - Cookie + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If consistent hash type is header, + the header field must be set. + rule: 'self.type == ''Header'' ? has(self.header) + : !has(self.header)' + - message: If consistent hash type is cookie, + the cookie field must be set. + rule: 'self.type == ''Cookie'' ? has(self.cookie) + : !has(self.cookie)' + slowStart: + description: |- + SlowStart defines the configuration related to the slow start load balancer policy. + If set, during slow start window, traffic sent to the newly added hosts will gradually increase. + Currently this is only supported for RoundRobin and LeastRequest load balancers + properties: + window: + description: |- + Window defines the duration of the warm up period for newly added host. + During slow start window, traffic sent to the newly added hosts will gradually increase. + Currently only supports linear growth of traffic. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#config-cluster-v3-cluster-slowstartconfig + type: string + required: + - window + type: object + type: + description: |- + Type decides the type of Load Balancer policy. + Valid LoadBalancerType values are + "ConsistentHash", + "LeastRequest", + "Random", + "RoundRobin". + enum: + - ConsistentHash + - LeastRequest + - Random + - RoundRobin + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If LoadBalancer type is consistentHash, + consistentHash field needs to be set. + rule: 'self.type == ''ConsistentHash'' ? has(self.consistentHash) + : !has(self.consistentHash)' + - message: Currently SlowStart is only supported + for RoundRobin and LeastRequest load balancers. + rule: 'self.type in [''Random'', ''ConsistentHash''] + ? !has(self.slowStart) : true ' + proxyProtocol: + description: ProxyProtocol enables the Proxy Protocol + when communicating with the backend. + properties: + version: + description: |- + Version of ProxyProtol + Valid ProxyProtocolVersion values are + "V1" + "V2" + enum: + - V1 + - V2 + type: string + required: + - version + type: object + retry: + description: |- + Retry provides more advanced usage, allowing users to customize the number of retries, retry fallback strategy, and retry triggering conditions. + If not set, retry will be disabled. + properties: + numRetries: + default: 2 + description: NumRetries is the number of retries + to be attempted. Defaults to 2. + format: int32 + minimum: 0 + type: integer + perRetry: + description: PerRetry is the retry policy to + be applied per retry attempt. + properties: + backOff: + description: |- + Backoff is the backoff policy to be applied per retry attempt. gateway uses a fully jittered exponential + back-off algorithm for retries. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#config-http-filters-router-x-envoy-max-retries + properties: + baseInterval: + description: BaseInterval is the base + interval between retries. + format: duration + type: string + maxInterval: + description: |- + MaxInterval is the maximum interval between retries. This parameter is optional, but must be greater than or equal to the base_interval if set. + The default is 10 times the base_interval + format: duration + type: string + type: object + timeout: + description: Timeout is the timeout per + retry attempt. + format: duration + type: string + type: object + retryOn: + description: |- + RetryOn specifies the retry trigger condition. + + If not specified, the default is to retry on connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes(503). + properties: + httpStatusCodes: + description: |- + HttpStatusCodes specifies the http status codes to be retried. + The retriable-status-codes trigger must also be configured for these status codes to trigger a retry. + items: + description: HTTPStatus defines the http + status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + triggers: + description: Triggers specifies the retry + trigger condition(Http/Grpc). + items: + description: TriggerEnum specifies the + conditions that trigger retries. + enum: + - 5xx + - gateway-error + - reset + - connect-failure + - retriable-4xx + - refused-stream + - retriable-status-codes + - cancelled + - deadline-exceeded + - internal + - resource-exhausted + - unavailable + type: string + type: array + type: object + type: object + tcpKeepalive: + description: |- + TcpKeepalive settings associated with the upstream client connection. + Disabled by default. + properties: + idleTime: + description: |- + The duration a connection needs to be idle before keep-alive + probes start being sent. + The duration format is + Defaults to `7200s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + The duration between keep-alive probes. + Defaults to `75s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + probes: + description: |- + The total number of unacknowledged probes to send before deciding + the connection is dead. + Defaults to 9. + format: int32 + type: integer + type: object + timeout: + description: Timeout settings for the backend connections. + properties: + http: + description: Timeout settings for HTTP. + properties: + connectionIdleTimeout: + description: |- + The idle timeout for an HTTP connection. Idle time is defined as a period in which there are no active requests in the connection. + Default: 1 hour. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + maxConnectionDuration: + description: |- + The maximum duration of an HTTP connection. + Default: unlimited. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + requestTimeout: + description: RequestTimeout is the time + until which entire response is received + from the upstream. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + tcp: + description: Timeout settings for TCP. + properties: + connectTimeout: + description: |- + The timeout for network connection establishment, including TCP and TLS handshakes. + Default: 10 seconds. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + type: object + type: object + uri: + description: |- + URI is the HTTPS URI to fetch the JWKS. Envoy's system trust bundle is used to validate the server certificate. + If a custom trust bundle is needed, it can be specified in a BackendTLSConfig resource and target the BackendRefs. + maxLength: 253 + minLength: 1 + type: string + required: + - uri + type: object + x-kubernetes-validations: + - message: BackendRefs must be used, backendRef is not supported. + rule: '!has(self.backendRef)' + - message: Retry timeout is not supported. + rule: has(self.backendSettings)? (has(self.backendSettings.retry)?(has(self.backendSettings.retry.perRetry)? + !has(self.backendSettings.retry.perRetry.timeout):true):true):true + - message: HTTPStatusCodes is not supported. + rule: has(self.backendSettings)? (has(self.backendSettings.retry)?(has(self.backendSettings.retry.retryOn)? + !has(self.backendSettings.retry.retryOn.httpStatusCodes):true):true):true + required: + - name + type: object + x-kubernetes-validations: + - message: claimToHeaders must be specified if recomputeRoute + is enabled. + rule: '(has(self.recomputeRoute) && self.recomputeRoute) ? + size(self.claimToHeaders) > 0 : true' + - message: either remoteJWKS or localJWKS must be specified. + rule: has(self.remoteJWKS) || has(self.localJWKS) + - message: remoteJWKS and localJWKS cannot both be specified. + rule: '!(has(self.remoteJWKS) && has(self.localJWKS))' + maxItems: 4 + minItems: 1 + type: array + required: + - providers + type: object + oidc: + description: OIDC defines the configuration for the OpenID Connect + (OIDC) authentication. + properties: + clientID: + description: |- + The client ID to be used in the OIDC + [Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest). + minLength: 1 + type: string + clientSecret: + description: |- + The Kubernetes secret which contains the OIDC client secret to be used in the + [Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest). + + This is an Opaque secret. The client secret should be stored in the key + "client-secret". + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + cookieDomain: + description: |- + The optional domain to set the access and ID token cookies on. + If not set, the cookies will default to the host of the request, not including the subdomains. + If set, the cookies will be set on the specified domain and all subdomains. + This means that requests to any subdomain will not require reauthentication after users log in to the parent domain. + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9]))*$ + type: string + cookieNames: + description: |- + The optional cookie name overrides to be used for Bearer and IdToken cookies in the + [Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest). + If not specified, uses a randomly generated suffix + properties: + accessToken: + description: |- + The name of the cookie used to store the AccessToken in the + [Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest). + If not specified, defaults to "AccessToken-(randomly generated uid)" + type: string + idToken: + description: |- + The name of the cookie used to store the IdToken in the + [Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest). + If not specified, defaults to "IdToken-(randomly generated uid)" + type: string + type: object + defaultRefreshTokenTTL: + description: |- + DefaultRefreshTokenTTL is the default lifetime of the refresh token. + This field is only used when the exp (expiration time) claim is omitted in + the refresh token or the refresh token is not JWT. + + If not specified, defaults to 604800s (one week). + Note: this field is only applicable when the "refreshToken" field is set to true. + type: string + defaultTokenTTL: + description: |- + DefaultTokenTTL is the default lifetime of the id token and access token. + Please note that Envoy will always use the expiry time from the response + of the authorization server if it is provided. This field is only used when + the expiry time is not provided by the authorization. + + If not specified, defaults to 0. In this case, the "expires_in" field in + the authorization response must be set by the authorization server, or the + OAuth flow will fail. + type: string + forwardAccessToken: + description: |- + ForwardAccessToken indicates whether the Envoy should forward the access token + via the Authorization header Bearer scheme to the upstream. + If not specified, defaults to false. + type: boolean + logoutPath: + description: |- + The path to log a user out, clearing their credential cookies. + + If not specified, uses a default logout path "/logout" + type: string + provider: + description: The OIDC Provider configuration. + properties: + authorizationEndpoint: + description: |- + The OIDC Provider's [authorization endpoint](https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint). + If not provided, EG will try to discover it from the provider's [Well-Known Configuration Endpoint](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse). + type: string + backendRef: + description: |- + BackendRef references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + + Deprecated: Use BackendRefs instead. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + backendRefs: + description: |- + BackendRefs references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + items: + description: BackendRef defines how an ObjectReference that + is specific to BackendRef. + properties: + fallback: + description: |- + Fallback indicates whether the backend is designated as a fallback. + Multiple fallback backends can be configured. + It is highly recommended to configure active or passive health checks to ensure that failover can be detected + when the active backends become unhealthy and to automatically readjust once the primary backends are healthy again. + The overprovisioning factor is set to 1.4, meaning the fallback backends will only start receiving traffic when + the health of the active backends falls below 72%. + type: boolean + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + backendSettings: + description: |- + BackendSettings holds configuration for managing the connection + to the backend. + properties: + circuitBreaker: + description: |- + Circuit Breaker settings for the upstream connections and requests. + If not set, circuit breakers will be enabled with the default thresholds + properties: + maxConnections: + default: 1024 + description: The maximum number of connections that + Envoy will establish to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRequests: + default: 1024 + description: The maximum number of parallel requests + that Envoy will make to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRetries: + default: 1024 + description: The maximum number of parallel retries + that Envoy will make to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxPendingRequests: + default: 1024 + description: The maximum number of pending requests + that Envoy will queue to the referenced backend + defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxRequestsPerConnection: + description: |- + The maximum number of requests that Envoy will make over a single connection to the referenced backend defined within a xRoute rule. + Default: unlimited. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + perEndpoint: + description: PerEndpoint defines Circuit Breakers + that will apply per-endpoint for an upstream cluster + properties: + maxConnections: + default: 1024 + description: MaxConnections configures the maximum + number of connections that Envoy will establish + per-endpoint to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + type: object + type: object + connection: + description: Connection includes backend connection settings. + properties: + bufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + BufferLimit Soft limit on size of the cluster’s connections read and write buffers. + BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space. + If unspecified, an implementation defined default is applied (32768 bytes). + For example, 20Mi, 1Gi, 256Ki etc. + Note: that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + socketBufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + SocketBufferLimit provides configuration for the maximum buffer size in bytes for each socket + to backend. + SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space. + For example, 20Mi, 1Gi, 256Ki etc. + Note that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + type: object + dns: + description: DNS includes dns resolution settings. + properties: + dnsRefreshRate: + description: |- + DNSRefreshRate specifies the rate at which DNS records should be refreshed. + Defaults to 30 seconds. + type: string + lookupFamily: + description: |- + LookupFamily determines how Envoy would resolve DNS for Routes where the backend is specified as a fully qualified domain name (FQDN). + If set, this configuration overrides other defaults. + enum: + - IPv4 + - IPv6 + - IPv4Preferred + - IPv6Preferred + - IPv4AndIPv6 + type: string + respectDnsTtl: + description: |- + RespectDNSTTL indicates whether the DNS Time-To-Live (TTL) should be respected. + If the value is set to true, the DNS refresh rate will be set to the resource record’s TTL. + Defaults to true. + type: boolean + type: object + healthCheck: + description: HealthCheck allows gateway to perform active + health checking on backends. + properties: + active: + description: Active health check configuration + properties: + grpc: + description: |- + GRPC defines the configuration of the GRPC health checker. + It's optional, and can only be used if the specified type is GRPC. + properties: + service: + description: |- + Service to send in the health check request. + If this is not specified, then the health check request applies to the entire + server and not to a specific service. + type: string + type: object + healthyThreshold: + default: 1 + description: HealthyThreshold defines the number + of healthy health checks required before a backend + host is marked healthy. + format: int32 + minimum: 1 + type: integer + http: + description: |- + HTTP defines the configuration of http health checker. + It's required while the health checker type is HTTP. + properties: + expectedResponse: + description: ExpectedResponse defines a list + of HTTP expected responses to match. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of + the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, binary + field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + expectedStatuses: + description: |- + ExpectedStatuses defines a list of HTTP response statuses considered healthy. + Defaults to 200 only + items: + description: HTTPStatus defines the http + status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + method: + description: |- + Method defines the HTTP method used for health checking. + Defaults to GET + type: string + path: + description: Path defines the HTTP path that + will be requested during health checking. + maxLength: 1024 + minLength: 1 + type: string + required: + - path + type: object + interval: + default: 3s + description: Interval defines the time between + active health checks. + format: duration + type: string + tcp: + description: |- + TCP defines the configuration of tcp health checker. + It's required while the health checker type is TCP. + properties: + receive: + description: Receive defines the expected + response payload. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of + the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, binary + field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + send: + description: Send defines the request payload. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of + the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, binary + field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + type: object + timeout: + default: 1s + description: Timeout defines the time to wait + for a health check response. + format: duration + type: string + type: + allOf: + - enum: + - HTTP + - TCP + - GRPC + - enum: + - HTTP + - TCP + - GRPC + description: Type defines the type of health checker. + type: string + unhealthyThreshold: + default: 3 + description: UnhealthyThreshold defines the number + of unhealthy health checks required before a + backend host is marked unhealthy. + format: int32 + minimum: 1 + type: integer + required: + - type + type: object + x-kubernetes-validations: + - message: If Health Checker type is HTTP, http field + needs to be set. + rule: 'self.type == ''HTTP'' ? has(self.http) : + !has(self.http)' + - message: If Health Checker type is TCP, tcp field + needs to be set. + rule: 'self.type == ''TCP'' ? has(self.tcp) : !has(self.tcp)' + - message: The grpc field can only be set if the Health + Checker type is GRPC. + rule: 'has(self.grpc) ? self.type == ''GRPC'' : + true' + panicThreshold: + description: |- + When number of unhealthy endpoints for a backend reaches this threshold + Envoy will disregard health status and balance across all endpoints. + It's designed to prevent a situation in which host failures cascade throughout the cluster + as load increases. If not set, the default value is 50%. To disable panic mode, set value to `0`. + format: int32 + maximum: 100 + minimum: 0 + type: integer + passive: + description: Passive passive check configuration + properties: + baseEjectionTime: + default: 30s + description: BaseEjectionTime defines the base + duration for which a host will be ejected on + consecutive failures. + format: duration + type: string + consecutive5XxErrors: + default: 5 + description: Consecutive5xxErrors sets the number + of consecutive 5xx errors triggering ejection. + format: int32 + type: integer + consecutiveGatewayErrors: + default: 0 + description: ConsecutiveGatewayErrors sets the + number of consecutive gateway errors triggering + ejection. + format: int32 + type: integer + consecutiveLocalOriginFailures: + default: 5 + description: |- + ConsecutiveLocalOriginFailures sets the number of consecutive local origin failures triggering ejection. + Parameter takes effect only when split_external_local_origin_errors is set to true. + format: int32 + type: integer + interval: + default: 3s + description: Interval defines the time between + passive health checks. + format: duration + type: string + maxEjectionPercent: + default: 10 + description: MaxEjectionPercent sets the maximum + percentage of hosts in a cluster that can be + ejected. + format: int32 + type: integer + splitExternalLocalOriginErrors: + default: false + description: SplitExternalLocalOriginErrors enables + splitting of errors between external and local + origin. + type: boolean + type: object + type: object + http2: + description: HTTP2 provides HTTP/2 configuration for backend + connections. + properties: + initialConnectionWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialConnectionWindowSize sets the initial window size for HTTP/2 connections. + If not set, the default value is 1 MiB. + x-kubernetes-int-or-string: true + initialStreamWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialStreamWindowSize sets the initial window size for HTTP/2 streams. + If not set, the default value is 64 KiB(64*1024). + x-kubernetes-int-or-string: true + maxConcurrentStreams: + description: |- + MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection. + If not set, the default value is 100. + format: int32 + maximum: 2147483647 + minimum: 1 + type: integer + onInvalidMessage: + description: |- + OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error + It's recommended for L2 Envoy deployments to set this value to TerminateStream. + https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two + Default: TerminateConnection + type: string + type: object + loadBalancer: + description: |- + LoadBalancer policy to apply when routing traffic from the gateway to + the backend endpoints. Defaults to `LeastRequest`. + properties: + consistentHash: + description: |- + ConsistentHash defines the configuration when the load balancer type is + set to ConsistentHash + properties: + cookie: + description: Cookie configures the cookie hash + policy when the consistent hash type is set + to Cookie. + properties: + attributes: + additionalProperties: + type: string + description: Additional Attributes to set + for the generated cookie. + type: object + name: + description: |- + Name of the cookie to hash. + If this cookie does not exist in the request, Envoy will generate a cookie and set + the TTL on the response back to the client based on Layer 4 + attributes of the backend endpoint, to ensure that these future requests + go to the same backend endpoint. Make sure to set the TTL field for this case. + type: string + ttl: + description: |- + TTL of the generated cookie if the cookie is not present. This value sets the + Max-Age attribute value. + type: string + required: + - name + type: object + header: + description: Header configures the header hash + policy when the consistent hash type is set + to Header. + properties: + name: + description: Name of the header to hash. + type: string + required: + - name + type: object + tableSize: + default: 65537 + description: The table size for consistent hashing, + must be prime number limited to 5000011. + format: int64 + maximum: 5000011 + minimum: 2 + type: integer + type: + description: |- + ConsistentHashType defines the type of input to hash on. Valid Type values are + "SourceIP", + "Header", + "Cookie". + enum: + - SourceIP + - Header + - Cookie + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If consistent hash type is header, the + header field must be set. + rule: 'self.type == ''Header'' ? has(self.header) + : !has(self.header)' + - message: If consistent hash type is cookie, the + cookie field must be set. + rule: 'self.type == ''Cookie'' ? has(self.cookie) + : !has(self.cookie)' + slowStart: + description: |- + SlowStart defines the configuration related to the slow start load balancer policy. + If set, during slow start window, traffic sent to the newly added hosts will gradually increase. + Currently this is only supported for RoundRobin and LeastRequest load balancers + properties: + window: + description: |- + Window defines the duration of the warm up period for newly added host. + During slow start window, traffic sent to the newly added hosts will gradually increase. + Currently only supports linear growth of traffic. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#config-cluster-v3-cluster-slowstartconfig + type: string + required: + - window + type: object + type: + description: |- + Type decides the type of Load Balancer policy. + Valid LoadBalancerType values are + "ConsistentHash", + "LeastRequest", + "Random", + "RoundRobin". + enum: + - ConsistentHash + - LeastRequest + - Random + - RoundRobin + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If LoadBalancer type is consistentHash, consistentHash + field needs to be set. + rule: 'self.type == ''ConsistentHash'' ? has(self.consistentHash) + : !has(self.consistentHash)' + - message: Currently SlowStart is only supported for RoundRobin + and LeastRequest load balancers. + rule: 'self.type in [''Random'', ''ConsistentHash''] + ? !has(self.slowStart) : true ' + proxyProtocol: + description: ProxyProtocol enables the Proxy Protocol + when communicating with the backend. + properties: + version: + description: |- + Version of ProxyProtol + Valid ProxyProtocolVersion values are + "V1" + "V2" + enum: + - V1 + - V2 + type: string + required: + - version + type: object + retry: + description: |- + Retry provides more advanced usage, allowing users to customize the number of retries, retry fallback strategy, and retry triggering conditions. + If not set, retry will be disabled. + properties: + numRetries: + default: 2 + description: NumRetries is the number of retries to + be attempted. Defaults to 2. + format: int32 + minimum: 0 + type: integer + perRetry: + description: PerRetry is the retry policy to be applied + per retry attempt. + properties: + backOff: + description: |- + Backoff is the backoff policy to be applied per retry attempt. gateway uses a fully jittered exponential + back-off algorithm for retries. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#config-http-filters-router-x-envoy-max-retries + properties: + baseInterval: + description: BaseInterval is the base interval + between retries. + format: duration + type: string + maxInterval: + description: |- + MaxInterval is the maximum interval between retries. This parameter is optional, but must be greater than or equal to the base_interval if set. + The default is 10 times the base_interval + format: duration + type: string + type: object + timeout: + description: Timeout is the timeout per retry + attempt. + format: duration + type: string + type: object + retryOn: + description: |- + RetryOn specifies the retry trigger condition. + + If not specified, the default is to retry on connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes(503). + properties: + httpStatusCodes: + description: |- + HttpStatusCodes specifies the http status codes to be retried. + The retriable-status-codes trigger must also be configured for these status codes to trigger a retry. + items: + description: HTTPStatus defines the http status + code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + triggers: + description: Triggers specifies the retry trigger + condition(Http/Grpc). + items: + description: TriggerEnum specifies the conditions + that trigger retries. + enum: + - 5xx + - gateway-error + - reset + - connect-failure + - retriable-4xx + - refused-stream + - retriable-status-codes + - cancelled + - deadline-exceeded + - internal + - resource-exhausted + - unavailable + type: string + type: array + type: object + type: object + tcpKeepalive: + description: |- + TcpKeepalive settings associated with the upstream client connection. + Disabled by default. + properties: + idleTime: + description: |- + The duration a connection needs to be idle before keep-alive + probes start being sent. + The duration format is + Defaults to `7200s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + The duration between keep-alive probes. + Defaults to `75s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + probes: + description: |- + The total number of unacknowledged probes to send before deciding + the connection is dead. + Defaults to 9. + format: int32 + type: integer + type: object + timeout: + description: Timeout settings for the backend connections. + properties: + http: + description: Timeout settings for HTTP. + properties: + connectionIdleTimeout: + description: |- + The idle timeout for an HTTP connection. Idle time is defined as a period in which there are no active requests in the connection. + Default: 1 hour. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + maxConnectionDuration: + description: |- + The maximum duration of an HTTP connection. + Default: unlimited. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + requestTimeout: + description: RequestTimeout is the time until + which entire response is received from the upstream. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + tcp: + description: Timeout settings for TCP. + properties: + connectTimeout: + description: |- + The timeout for network connection establishment, including TCP and TLS handshakes. + Default: 10 seconds. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + type: object + type: object + issuer: + description: |- + The OIDC Provider's [issuer identifier](https://openid.net/specs/openid-connect-discovery-1_0.html#IssuerDiscovery). + Issuer MUST be a URI RFC 3986 [RFC3986] with a scheme component that MUST + be https, a host component, and optionally, port and path components and + no query or fragment components. + minLength: 1 + type: string + tokenEndpoint: + description: |- + The OIDC Provider's [token endpoint](https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint). + If not provided, EG will try to discover it from the provider's [Well-Known Configuration Endpoint](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse). + type: string + required: + - issuer + type: object + x-kubernetes-validations: + - message: BackendRefs must be used, backendRef is not supported. + rule: '!has(self.backendRef)' + - message: Retry timeout is not supported. + rule: has(self.backendSettings)? (has(self.backendSettings.retry)?(has(self.backendSettings.retry.perRetry)? + !has(self.backendSettings.retry.perRetry.timeout):true):true):true + - message: HTTPStatusCodes is not supported. + rule: has(self.backendSettings)? (has(self.backendSettings.retry)?(has(self.backendSettings.retry.retryOn)? + !has(self.backendSettings.retry.retryOn.httpStatusCodes):true):true):true + redirectURL: + description: |- + The redirect URL to be used in the OIDC + [Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest). + If not specified, uses the default redirect URI "%REQ(x-forwarded-proto)%://%REQ(:authority)%/oauth2/callback" + type: string + refreshToken: + description: |- + RefreshToken indicates whether the Envoy should automatically refresh the + id token and access token when they expire. + When set to true, the Envoy will use the refresh token to get a new id token + and access token when they expire. + + If not specified, defaults to false. + type: boolean + resources: + description: |- + The OIDC resources to be used in the + [Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest). + items: + type: string + type: array + scopes: + description: |- + The OIDC scopes to be used in the + [Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest). + The "openid" scope is always added to the list of scopes if not already + specified. + items: + type: string + type: array + required: + - clientID + - clientSecret + - provider + type: object + targetRef: + description: |- + TargetRef is the name of the resource this policy is being attached to. + This policy and the TargetRef MUST be in the same namespace for this + Policy to have effect + + Deprecated: use targetRefs/targetSelectors instead + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + targetRefs: + description: |- + TargetRefs are the names of the Gateway resources this policy + is being attached to. + items: + description: |- + LocalPolicyTargetReferenceWithSectionName identifies an API object to apply a + direct policy to. This should be used as part of Policy resources that can + target single resources. For more information on how this policy attachment + mode works, and a sample Policy resource, refer to the policy attachment + documentation for Gateway API. + + Note: This should only be used for direct policy attachment when references + to SectionName are actually needed. In all other cases, + LocalPolicyTargetReference should be used. + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + type: array + targetSelectors: + description: TargetSelectors allow targeting resources for this policy + based on labels + items: + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group that this selector targets. + Defaults to gateway.networking.k8s.io + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the resource kind that this selector targets. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: MatchLabels are the set of label selectors for + identifying the targeted resource + type: object + required: + - kind + type: object + x-kubernetes-validations: + - message: group must be gateway.networking.k8s.io + rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' + : true ' + type: array + type: object + x-kubernetes-validations: + - message: either targetRef or targetRefs must be used + rule: '(has(self.targetRef) && !has(self.targetRefs)) || (!has(self.targetRef) + && has(self.targetRefs)) || (has(self.targetSelectors) && self.targetSelectors.size() + > 0) ' + - message: this policy can only have a targetRef.group of gateway.networking.k8s.io + rule: 'has(self.targetRef) ? self.targetRef.group == ''gateway.networking.k8s.io'' + : true' + - message: this policy can only have a targetRef.kind of Gateway/HTTPRoute/GRPCRoute + rule: 'has(self.targetRef) ? self.targetRef.kind in [''Gateway'', ''HTTPRoute'', + ''GRPCRoute''] : true' + - message: this policy does not yet support the sectionName field + rule: 'has(self.targetRef) ? !has(self.targetRef.sectionName) : true' + - message: this policy can only have a targetRefs[*].group of gateway.networking.k8s.io + rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, ref.group == + ''gateway.networking.k8s.io'') : true ' + - message: this policy can only have a targetRefs[*].kind of Gateway/HTTPRoute/GRPCRoute + rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, ref.kind in [''Gateway'', + ''HTTPRoute'', ''GRPCRoute'']) : true ' + - message: this policy does not yet support the sectionName field + rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, !has(ref.sectionName)) + : true' + - message: if authorization.rules.principal.jwt is used, jwt must be defined + rule: '(has(self.authorization) && has(self.authorization.rules) && + self.authorization.rules.exists(r, has(r.principal.jwt))) ? has(self.jwt) + : true' + status: + description: Status defines the current status of SecurityPolicy. + properties: + ancestors: + description: |- + Ancestors is a list of ancestor resources (usually Gateways) that are + associated with the policy, and the status of the policy with respect to + each ancestor. When this policy attaches to a parent, the controller that + manages the parent and the ancestors MUST add an entry to this list when + the controller first sees the policy and SHOULD update the entry as + appropriate when the relevant ancestor is modified. + + Note that choosing the relevant ancestor is left to the Policy designers; + an important part of Policy design is designing the right object level at + which to namespace this status. + + Note also that implementations MUST ONLY populate ancestor status for + the Ancestor resources they are responsible for. Implementations MUST + use the ControllerName field to uniquely identify the entries in this list + that they are responsible for. + + Note that to achieve this, the list of PolicyAncestorStatus structs + MUST be treated as a map with a composite key, made up of the AncestorRef + and ControllerName fields combined. + + A maximum of 16 ancestors will be represented in this list. An empty list + means the Policy is not relevant for any ancestors. + + If this slice is full, implementations MUST NOT add further entries. + Instead they MUST consider the policy unimplementable and signal that + on any related resources such as the ancestor that would be referenced + here. For example, if this list was full on BackendTLSPolicy, no + additional Gateways would be able to reference the Service targeted by + the BackendTLSPolicy. + items: + description: |- + PolicyAncestorStatus describes the status of a route with respect to an + associated Ancestor. + + Ancestors refer to objects that are either the Target of a policy or above it + in terms of object hierarchy. For example, if a policy targets a Service, the + Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and + the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most + useful object to place Policy status on, so we recommend that implementations + SHOULD use Gateway as the PolicyAncestorStatus object unless the designers + have a _very_ good reason otherwise. + + In the context of policy attachment, the Ancestor is used to distinguish which + resource results in a distinct application of this policy. For example, if a policy + targets a Service, it may have a distinct result per attached Gateway. + + Policies targeting the same resource may have different effects depending on the + ancestors of those resources. For example, different Gateways targeting the same + Service may have different capabilities, especially if they have different underlying + implementations. + + For example, in BackendTLSPolicy, the Policy attaches to a Service that is + used as a backend in a HTTPRoute that is itself attached to a Gateway. + In this case, the relevant object for status is the Gateway, and that is the + ancestor object referred to in this status. + + Note that a parent is also an ancestor, so for objects where the parent is the + relevant object for status, this struct SHOULD still be used. + + This struct is intended to be used in a slice that's effectively a map, + with a composite key made up of the AncestorRef and the ControllerName. + properties: + ancestorRef: + description: |- + AncestorRef corresponds with a ParentRef in the spec that this + PolicyAncestorStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: Conditions describes the status of the Policy with + respect to the given Ancestor. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - controllerName + type: object + maxItems: 16 + type: array + required: + - ancestors + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Copyright 2025 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Gateway API Experimental channel install +# diff --git a/test/helm/gateway-crds-helm/default.in.yaml b/test/helm/gateway-crds-helm/default.in.yaml new file mode 100644 index 0000000000..d1433de319 --- /dev/null +++ b/test/helm/gateway-crds-helm/default.in.yaml @@ -0,0 +1,5 @@ +crds: + gatewayAPI: + enabled: false + envoyGateway: + enabled: false diff --git a/test/helm/gateway-crds-helm/default.out.yaml b/test/helm/gateway-crds-helm/default.out.yaml new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/test/helm/gateway-crds-helm/default.out.yaml @@ -0,0 +1 @@ + diff --git a/test/helm/gateway-crds-helm/envoy-gateway-crds.in.yaml b/test/helm/gateway-crds-helm/envoy-gateway-crds.in.yaml new file mode 100644 index 0000000000..095e7c2b7c --- /dev/null +++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.in.yaml @@ -0,0 +1,5 @@ +crds: + gatewayAPI: + enabled: false + envoyGateway: + enabled: true diff --git a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml new file mode 100644 index 0000000000..c478588002 --- /dev/null +++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml @@ -0,0 +1,26130 @@ +--- +# Source: gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backends.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 + name: backends.gateway.envoyproxy.io +spec: + group: gateway.envoyproxy.io + names: + categories: + - envoy-gateway + kind: Backend + listKind: BackendList + plural: backends + shortNames: + - be + singular: backend + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Accepted")].reason + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + Backend allows the user to configure the endpoints of a backend and + the behavior of the connection from Envoy Proxy to the backend. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of Backend. + properties: + appProtocols: + description: AppProtocols defines the application protocols to be + supported when connecting to the backend. + items: + description: AppProtocolType defines various backend applications + protocols supported by Envoy Gateway + enum: + - gateway.envoyproxy.io/h2c + - gateway.envoyproxy.io/ws + - gateway.envoyproxy.io/wss + type: string + type: array + endpoints: + description: Endpoints defines the endpoints to be used when connecting + to the backend. + items: + description: |- + BackendEndpoint describes a backend endpoint, which can be either a fully-qualified domain name, IP address or unix domain socket + corresponding to Envoy's Address: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto#config-core-v3-address + properties: + fqdn: + description: FQDN defines a FQDN endpoint + properties: + hostname: + description: Hostname defines the FQDN hostname of the backend + endpoint. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: Port defines the port of the backend endpoint. + format: int32 + maximum: 65535 + minimum: 0 + type: integer + required: + - hostname + - port + type: object + ip: + description: IP defines an IP endpoint. Supports both IPv4 and + IPv6 addresses. + properties: + address: + description: |- + Address defines the IP address of the backend endpoint. + Supports both IPv4 and IPv6 addresses. + maxLength: 45 + minLength: 3 + pattern: ^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|^(([0-9a-fA-F]{1,4}:){1,7}[0-9a-fA-F]{1,4}|::|(([0-9a-fA-F]{1,4}:){0,5})?(:[0-9a-fA-F]{1,4}){1,2})$ + type: string + port: + description: Port defines the port of the backend endpoint. + format: int32 + maximum: 65535 + minimum: 0 + type: integer + required: + - address + - port + type: object + unix: + description: Unix defines the unix domain socket endpoint + properties: + path: + description: Path defines the unix domain socket path of + the backend endpoint. + type: string + required: + - path + type: object + type: object + x-kubernetes-validations: + - message: one of fqdn, ip or unix must be specified + rule: (has(self.fqdn) || has(self.ip) || has(self.unix)) + - message: only one of fqdn, ip or unix can be specified + rule: ((has(self.fqdn) && !(has(self.ip) || has(self.unix))) || + (has(self.ip) && !(has(self.fqdn) || has(self.unix))) || (has(self.unix) + && !(has(self.ip) || has(self.fqdn)))) + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-validations: + - message: fqdn addresses cannot be mixed with other address types + rule: self.all(f, has(f.fqdn)) || !self.exists(f, has(f.fqdn)) + fallback: + description: |- + Fallback indicates whether the backend is designated as a fallback. + It is highly recommended to configure active or passive health checks to ensure that failover can be detected + when the active backends become unhealthy and to automatically readjust once the primary backends are healthy again. + The overprovisioning factor is set to 1.4, meaning the fallback backends will only start receiving traffic when + the health of the active backends falls below 72%. + type: boolean + tls: + description: |- + TLS defines the TLS settings for the backend. + Only supported for DynamicResolver backends. + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to Kubernetes objects that + contain TLS certificates of the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the backend. + + A single reference to a Kubernetes ConfigMap or a Kubernetes Secret, + with the CA certificate in a key named `ca.crt` is currently supported. + + If CACertificateRefs is empty or unspecified, then WellKnownCACertificates must be + specified. Only one of CACertificateRefs or WellKnownCACertificates may be specified, + not both. + items: + description: |- + LocalObjectReference identifies an API object within the namespace of the + referrer. + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" + or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + maxItems: 8 + type: array + wellKnownCACertificates: + description: |- + WellKnownCACertificates specifies whether system CA certificates may be used in + the TLS handshake between the gateway and backend pod. + + If WellKnownCACertificates is unspecified or empty (""), then CACertificateRefs + must be specified with at least one entry for a valid configuration. Only one of + CACertificateRefs or WellKnownCACertificates may be specified, not both. + enum: + - System + type: string + type: object + type: + default: Endpoints + description: Type defines the type of the backend. Defaults to "Endpoints" + enum: + - Endpoints + - DynamicResolver + type: string + type: object + x-kubernetes-validations: + - message: DynamicResolver type cannot have endpoints and appProtocols + specified + rule: self.type != 'DynamicResolver' || !has(self.endpoints) && !has(self.appProtocols) + status: + description: Status defines the current status of Backend. + properties: + conditions: + description: Conditions describe the current conditions of the Backend. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 + name: backendtrafficpolicies.gateway.envoyproxy.io +spec: + group: gateway.envoyproxy.io + names: + categories: + - envoy-gateway + kind: BackendTrafficPolicy + listKind: BackendTrafficPolicyList + plural: backendtrafficpolicies + shortNames: + - btp + singular: backendtrafficpolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + BackendTrafficPolicy allows the user to configure the behavior of the connection + between the Envoy Proxy listener and the backend service. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: spec defines the desired state of BackendTrafficPolicy. + properties: + circuitBreaker: + description: |- + Circuit Breaker settings for the upstream connections and requests. + If not set, circuit breakers will be enabled with the default thresholds + properties: + maxConnections: + default: 1024 + description: The maximum number of connections that Envoy will + establish to the referenced backend defined within a xRoute + rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRequests: + default: 1024 + description: The maximum number of parallel requests that Envoy + will make to the referenced backend defined within a xRoute + rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRetries: + default: 1024 + description: The maximum number of parallel retries that Envoy + will make to the referenced backend defined within a xRoute + rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxPendingRequests: + default: 1024 + description: The maximum number of pending requests that Envoy + will queue to the referenced backend defined within a xRoute + rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxRequestsPerConnection: + description: |- + The maximum number of requests that Envoy will make over a single connection to the referenced backend defined within a xRoute rule. + Default: unlimited. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + perEndpoint: + description: PerEndpoint defines Circuit Breakers that will apply + per-endpoint for an upstream cluster + properties: + maxConnections: + default: 1024 + description: MaxConnections configures the maximum number + of connections that Envoy will establish per-endpoint to + the referenced backend defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + type: object + type: object + compression: + description: The compression config for the http streams. + items: + description: |- + Compression defines the config of enabling compression. + This can help reduce the bandwidth at the expense of higher CPU. + properties: + brotli: + description: The configuration for Brotli compressor. + type: object + gzip: + description: The configuration for GZIP compressor. + type: object + type: + description: CompressorType defines the compressor type to use + for compression. + enum: + - Gzip + - Brotli + type: string + required: + - type + type: object + type: array + connection: + description: Connection includes backend connection settings. + properties: + bufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + BufferLimit Soft limit on size of the cluster’s connections read and write buffers. + BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space. + If unspecified, an implementation defined default is applied (32768 bytes). + For example, 20Mi, 1Gi, 256Ki etc. + Note: that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + socketBufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + SocketBufferLimit provides configuration for the maximum buffer size in bytes for each socket + to backend. + SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space. + For example, 20Mi, 1Gi, 256Ki etc. + Note that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + type: object + dns: + description: DNS includes dns resolution settings. + properties: + dnsRefreshRate: + description: |- + DNSRefreshRate specifies the rate at which DNS records should be refreshed. + Defaults to 30 seconds. + type: string + lookupFamily: + description: |- + LookupFamily determines how Envoy would resolve DNS for Routes where the backend is specified as a fully qualified domain name (FQDN). + If set, this configuration overrides other defaults. + enum: + - IPv4 + - IPv6 + - IPv4Preferred + - IPv6Preferred + - IPv4AndIPv6 + type: string + respectDnsTtl: + description: |- + RespectDNSTTL indicates whether the DNS Time-To-Live (TTL) should be respected. + If the value is set to true, the DNS refresh rate will be set to the resource record’s TTL. + Defaults to true. + type: boolean + type: object + faultInjection: + description: |- + FaultInjection defines the fault injection policy to be applied. This configuration can be used to + inject delays and abort requests to mimic failure scenarios such as service failures and overloads + properties: + abort: + description: If specified, the request will be aborted if it meets + the configuration criteria. + properties: + grpcStatus: + description: GrpcStatus specifies the GRPC status code to + be returned + format: int32 + type: integer + httpStatus: + description: StatusCode specifies the HTTP status code to + be returned + format: int32 + maximum: 600 + minimum: 200 + type: integer + percentage: + default: 100 + description: Percentage specifies the percentage of requests + to be aborted. Default 100%, if set 0, no requests will + be aborted. Accuracy to 0.0001%. + type: number + type: object + x-kubernetes-validations: + - message: httpStatus and grpcStatus cannot be simultaneously + defined. + rule: ' !(has(self.httpStatus) && has(self.grpcStatus)) ' + - message: httpStatus and grpcStatus are set at least one. + rule: ' has(self.httpStatus) || has(self.grpcStatus) ' + delay: + description: If specified, a delay will be injected into the request. + properties: + fixedDelay: + description: FixedDelay specifies the fixed delay duration + type: string + percentage: + default: 100 + description: Percentage specifies the percentage of requests + to be delayed. Default 100%, if set 0, no requests will + be delayed. Accuracy to 0.0001%. + type: number + required: + - fixedDelay + type: object + type: object + x-kubernetes-validations: + - message: Delay and abort faults are set at least one. + rule: ' has(self.delay) || has(self.abort) ' + healthCheck: + description: HealthCheck allows gateway to perform active health checking + on backends. + properties: + active: + description: Active health check configuration + properties: + grpc: + description: |- + GRPC defines the configuration of the GRPC health checker. + It's optional, and can only be used if the specified type is GRPC. + properties: + service: + description: |- + Service to send in the health check request. + If this is not specified, then the health check request applies to the entire + server and not to a specific service. + type: string + type: object + healthyThreshold: + default: 1 + description: HealthyThreshold defines the number of healthy + health checks required before a backend host is marked healthy. + format: int32 + minimum: 1 + type: integer + http: + description: |- + HTTP defines the configuration of http health checker. + It's required while the health checker type is HTTP. + properties: + expectedResponse: + description: ExpectedResponse defines a list of HTTP expected + responses to match. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field needs to + be set. + rule: 'self.type == ''Text'' ? has(self.text) : !has(self.text)' + - message: If payload type is Binary, binary field needs + to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) : + !has(self.binary)' + expectedStatuses: + description: |- + ExpectedStatuses defines a list of HTTP response statuses considered healthy. + Defaults to 200 only + items: + description: HTTPStatus defines the http status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + method: + description: |- + Method defines the HTTP method used for health checking. + Defaults to GET + type: string + path: + description: Path defines the HTTP path that will be requested + during health checking. + maxLength: 1024 + minLength: 1 + type: string + required: + - path + type: object + interval: + default: 3s + description: Interval defines the time between active health + checks. + format: duration + type: string + tcp: + description: |- + TCP defines the configuration of tcp health checker. + It's required while the health checker type is TCP. + properties: + receive: + description: Receive defines the expected response payload. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field needs to + be set. + rule: 'self.type == ''Text'' ? has(self.text) : !has(self.text)' + - message: If payload type is Binary, binary field needs + to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) : + !has(self.binary)' + send: + description: Send defines the request payload. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field needs to + be set. + rule: 'self.type == ''Text'' ? has(self.text) : !has(self.text)' + - message: If payload type is Binary, binary field needs + to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) : + !has(self.binary)' + type: object + timeout: + default: 1s + description: Timeout defines the time to wait for a health + check response. + format: duration + type: string + type: + allOf: + - enum: + - HTTP + - TCP + - GRPC + - enum: + - HTTP + - TCP + - GRPC + description: Type defines the type of health checker. + type: string + unhealthyThreshold: + default: 3 + description: UnhealthyThreshold defines the number of unhealthy + health checks required before a backend host is marked unhealthy. + format: int32 + minimum: 1 + type: integer + required: + - type + type: object + x-kubernetes-validations: + - message: If Health Checker type is HTTP, http field needs to + be set. + rule: 'self.type == ''HTTP'' ? has(self.http) : !has(self.http)' + - message: If Health Checker type is TCP, tcp field needs to be + set. + rule: 'self.type == ''TCP'' ? has(self.tcp) : !has(self.tcp)' + - message: The grpc field can only be set if the Health Checker + type is GRPC. + rule: 'has(self.grpc) ? self.type == ''GRPC'' : true' + panicThreshold: + description: |- + When number of unhealthy endpoints for a backend reaches this threshold + Envoy will disregard health status and balance across all endpoints. + It's designed to prevent a situation in which host failures cascade throughout the cluster + as load increases. If not set, the default value is 50%. To disable panic mode, set value to `0`. + format: int32 + maximum: 100 + minimum: 0 + type: integer + passive: + description: Passive passive check configuration + properties: + baseEjectionTime: + default: 30s + description: BaseEjectionTime defines the base duration for + which a host will be ejected on consecutive failures. + format: duration + type: string + consecutive5XxErrors: + default: 5 + description: Consecutive5xxErrors sets the number of consecutive + 5xx errors triggering ejection. + format: int32 + type: integer + consecutiveGatewayErrors: + default: 0 + description: ConsecutiveGatewayErrors sets the number of consecutive + gateway errors triggering ejection. + format: int32 + type: integer + consecutiveLocalOriginFailures: + default: 5 + description: |- + ConsecutiveLocalOriginFailures sets the number of consecutive local origin failures triggering ejection. + Parameter takes effect only when split_external_local_origin_errors is set to true. + format: int32 + type: integer + interval: + default: 3s + description: Interval defines the time between passive health + checks. + format: duration + type: string + maxEjectionPercent: + default: 10 + description: MaxEjectionPercent sets the maximum percentage + of hosts in a cluster that can be ejected. + format: int32 + type: integer + splitExternalLocalOriginErrors: + default: false + description: SplitExternalLocalOriginErrors enables splitting + of errors between external and local origin. + type: boolean + type: object + type: object + http2: + description: HTTP2 provides HTTP/2 configuration for backend connections. + properties: + initialConnectionWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialConnectionWindowSize sets the initial window size for HTTP/2 connections. + If not set, the default value is 1 MiB. + x-kubernetes-int-or-string: true + initialStreamWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialStreamWindowSize sets the initial window size for HTTP/2 streams. + If not set, the default value is 64 KiB(64*1024). + x-kubernetes-int-or-string: true + maxConcurrentStreams: + description: |- + MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection. + If not set, the default value is 100. + format: int32 + maximum: 2147483647 + minimum: 1 + type: integer + onInvalidMessage: + description: |- + OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error + It's recommended for L2 Envoy deployments to set this value to TerminateStream. + https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two + Default: TerminateConnection + type: string + type: object + httpUpgrade: + description: |- + HTTPUpgrade defines the configuration for HTTP protocol upgrades. + If not specified, the default upgrade configuration(websocket) will be used. + items: + properties: + type: + description: |- + Type is the case-insensitive type of protocol upgrade. + e.g. `websocket`, `CONNECT`, `spdy/3.1` etc. + type: string + required: + - type + type: object + type: array + loadBalancer: + description: |- + LoadBalancer policy to apply when routing traffic from the gateway to + the backend endpoints. Defaults to `LeastRequest`. + properties: + consistentHash: + description: |- + ConsistentHash defines the configuration when the load balancer type is + set to ConsistentHash + properties: + cookie: + description: Cookie configures the cookie hash policy when + the consistent hash type is set to Cookie. + properties: + attributes: + additionalProperties: + type: string + description: Additional Attributes to set for the generated + cookie. + type: object + name: + description: |- + Name of the cookie to hash. + If this cookie does not exist in the request, Envoy will generate a cookie and set + the TTL on the response back to the client based on Layer 4 + attributes of the backend endpoint, to ensure that these future requests + go to the same backend endpoint. Make sure to set the TTL field for this case. + type: string + ttl: + description: |- + TTL of the generated cookie if the cookie is not present. This value sets the + Max-Age attribute value. + type: string + required: + - name + type: object + header: + description: Header configures the header hash policy when + the consistent hash type is set to Header. + properties: + name: + description: Name of the header to hash. + type: string + required: + - name + type: object + tableSize: + default: 65537 + description: The table size for consistent hashing, must be + prime number limited to 5000011. + format: int64 + maximum: 5000011 + minimum: 2 + type: integer + type: + description: |- + ConsistentHashType defines the type of input to hash on. Valid Type values are + "SourceIP", + "Header", + "Cookie". + enum: + - SourceIP + - Header + - Cookie + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If consistent hash type is header, the header field + must be set. + rule: 'self.type == ''Header'' ? has(self.header) : !has(self.header)' + - message: If consistent hash type is cookie, the cookie field + must be set. + rule: 'self.type == ''Cookie'' ? has(self.cookie) : !has(self.cookie)' + slowStart: + description: |- + SlowStart defines the configuration related to the slow start load balancer policy. + If set, during slow start window, traffic sent to the newly added hosts will gradually increase. + Currently this is only supported for RoundRobin and LeastRequest load balancers + properties: + window: + description: |- + Window defines the duration of the warm up period for newly added host. + During slow start window, traffic sent to the newly added hosts will gradually increase. + Currently only supports linear growth of traffic. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#config-cluster-v3-cluster-slowstartconfig + type: string + required: + - window + type: object + type: + description: |- + Type decides the type of Load Balancer policy. + Valid LoadBalancerType values are + "ConsistentHash", + "LeastRequest", + "Random", + "RoundRobin". + enum: + - ConsistentHash + - LeastRequest + - Random + - RoundRobin + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If LoadBalancer type is consistentHash, consistentHash + field needs to be set. + rule: 'self.type == ''ConsistentHash'' ? has(self.consistentHash) + : !has(self.consistentHash)' + - message: Currently SlowStart is only supported for RoundRobin and + LeastRequest load balancers. + rule: 'self.type in [''Random'', ''ConsistentHash''] ? !has(self.slowStart) + : true ' + mergeType: + description: |- + MergeType determines how this configuration is merged with existing BackendTrafficPolicy + configurations targeting a parent resource. When set, this configuration will be merged + into a parent BackendTrafficPolicy (i.e. the one targeting a Gateway or Listener). + This field cannot be set when targeting a parent resource (Gateway). + If unset, no merging occurs, and only the most specific configuration takes effect. + type: string + proxyProtocol: + description: ProxyProtocol enables the Proxy Protocol when communicating + with the backend. + properties: + version: + description: |- + Version of ProxyProtol + Valid ProxyProtocolVersion values are + "V1" + "V2" + enum: + - V1 + - V2 + type: string + required: + - version + type: object + rateLimit: + description: |- + RateLimit allows the user to limit the number of incoming requests + to a predefined value based on attributes within the traffic flow. + properties: + global: + description: Global defines global rate limit configuration. + properties: + rules: + description: |- + Rules are a list of RateLimit selectors and limits. Each rule and its + associated limit is applied in a mutually exclusive way. If a request + matches multiple rules, each of their associated limits get applied, so a + single request might increase the rate limit counters for multiple rules + if selected. The rate limit service will return a logical OR of the individual + rate limit decisions of all matching rules. For example, if a request + matches two rules, one rate limited and one not, the final decision will be + to rate limit the request. + items: + description: |- + RateLimitRule defines the semantics for matching attributes + from the incoming requests, and setting limits for them. + properties: + clientSelectors: + description: |- + 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 no client selectors are specified, the rule applies to all traffic of + the targeted Route. + + If the policy targets a Gateway, the rule applies to each Route of the Gateway. + Please note that each Route has its own rate limit counters. For example, + if a Gateway has two Routes, and the policy has a rule with limit 10rps, + each Route will have its own 10rps limit. + items: + description: |- + 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. + properties: + headers: + description: |- + Headers is a list of request headers to match. Multiple header values are ANDed together, + meaning, a request MUST match all the specified headers. + At least one of headers or sourceCIDR condition must be specified. + items: + description: HeaderMatch defines the match attributes + within the HTTP Headers of the request. + properties: + invert: + default: false + description: |- + Invert specifies whether the value match result will be inverted. + Do not set this field when Type="Distinct", implying matching on any/all unique + values within the header. + type: boolean + name: + description: |- + Name of the HTTP header. + The header name is case-insensitive unless PreserveHeaderCase is set to true. + For example, "Foo" and "foo" are considered the same header. + maxLength: 256 + minLength: 1 + type: string + type: + default: Exact + description: Type specifies how to match + against the value of the header. + enum: + - Exact + - RegularExpression + - Distinct + type: string + value: + description: |- + Value within the HTTP header. + Do not set this field when Type="Distinct", implying matching on any/all unique + values within the header. + maxLength: 1024 + type: string + required: + - name + type: object + maxItems: 16 + type: array + sourceCIDR: + description: |- + SourceCIDR is the client IP Address range to match on. + At least one of headers or sourceCIDR condition must be specified. + properties: + type: + default: Exact + enum: + - Exact + - Distinct + type: string + value: + description: |- + Value 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`. + maxLength: 256 + minLength: 1 + type: string + required: + - value + type: object + type: object + maxItems: 8 + type: array + cost: + description: |- + Cost specifies the cost of requests and responses for the rule. + + This is optional and if not specified, the default behavior is to reduce the rate limit counters by 1 on + the request path and do not reduce the rate limit counters on the response path. + properties: + request: + description: |- + Request specifies the number to reduce the rate limit counters + on the request path. If this is not specified, the default behavior + is to reduce the rate limit counters by 1. + + When Envoy receives a request that matches the rule, it tries to reduce the + rate limit counters by the specified number. If the counter doesn't have + enough capacity, the request is rate limited. + properties: + from: + description: From specifies where to get the + rate limit cost. Currently, only "Number" + and "Metadata" are supported. + enum: + - Number + - Metadata + type: string + metadata: + description: Metadata specifies the per-request + metadata to retrieve the usage number from. + properties: + key: + description: Key is the key to retrieve + the usage number from the filter metadata. + type: string + namespace: + description: Namespace is the namespace + of the dynamic metadata. + type: string + required: + - key + - namespace + type: object + number: + description: |- + Number specifies the fixed usage number to reduce the rate limit counters. + Using zero can be used to only check the rate limit counters without reducing them. + format: int64 + type: integer + required: + - from + type: object + x-kubernetes-validations: + - message: only one of number or metadata can be + specified + rule: '!(has(self.number) && has(self.metadata))' + response: + description: |- + Response specifies the number to reduce the rate limit counters + after the response is sent back to the client or the request stream is closed. + + The cost is used to reduce the rate limit counters for the matching requests. + Since the reduction happens after the request stream is complete, the rate limit + won't be enforced for the current request, but for the subsequent matching requests. + + This is optional and if not specified, the rate limit counters are not reduced + on the response path. + + Currently, this is only supported for HTTP Global Rate Limits. + properties: + from: + description: From specifies where to get the + rate limit cost. Currently, only "Number" + and "Metadata" are supported. + enum: + - Number + - Metadata + type: string + metadata: + description: Metadata specifies the per-request + metadata to retrieve the usage number from. + properties: + key: + description: Key is the key to retrieve + the usage number from the filter metadata. + type: string + namespace: + description: Namespace is the namespace + of the dynamic metadata. + type: string + required: + - key + - namespace + type: object + number: + description: |- + Number specifies the fixed usage number to reduce the rate limit counters. + Using zero can be used to only check the rate limit counters without reducing them. + format: int64 + type: integer + required: + - from + type: object + x-kubernetes-validations: + - message: only one of number or metadata can be + specified + rule: '!(has(self.number) && has(self.metadata))' + type: object + limit: + description: |- + 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. + properties: + requests: + type: integer + unit: + description: |- + RateLimitUnit specifies the intervals for setting rate limits. + Valid RateLimitUnit values are "Second", "Minute", "Hour", and "Day". + enum: + - Second + - Minute + - Hour + - Day + type: string + required: + - requests + - unit + type: object + required: + - limit + type: object + maxItems: 64 + type: array + shared: + default: false + description: |- + Shared determines whether the rate limit rules apply across all the policy targets. + If set to true, the rule is treated as a common bucket and is shared across all policy targets (xRoutes). + Default: false. + type: boolean + required: + - rules + type: object + local: + description: Local defines local rate limit configuration. + properties: + rules: + description: |- + Rules are a list of RateLimit selectors and limits. If a request matches + multiple rules, the strictest limit is applied. For example, if a request + matches two rules, one with 10rps and one with 20rps, the final limit will + be based on the rule with 10rps. + items: + description: |- + RateLimitRule defines the semantics for matching attributes + from the incoming requests, and setting limits for them. + properties: + clientSelectors: + description: |- + 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 no client selectors are specified, the rule applies to all traffic of + the targeted Route. + + If the policy targets a Gateway, the rule applies to each Route of the Gateway. + Please note that each Route has its own rate limit counters. For example, + if a Gateway has two Routes, and the policy has a rule with limit 10rps, + each Route will have its own 10rps limit. + items: + description: |- + 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. + properties: + headers: + description: |- + Headers is a list of request headers to match. Multiple header values are ANDed together, + meaning, a request MUST match all the specified headers. + At least one of headers or sourceCIDR condition must be specified. + items: + description: HeaderMatch defines the match attributes + within the HTTP Headers of the request. + properties: + invert: + default: false + description: |- + Invert specifies whether the value match result will be inverted. + Do not set this field when Type="Distinct", implying matching on any/all unique + values within the header. + type: boolean + name: + description: |- + Name of the HTTP header. + The header name is case-insensitive unless PreserveHeaderCase is set to true. + For example, "Foo" and "foo" are considered the same header. + maxLength: 256 + minLength: 1 + type: string + type: + default: Exact + description: Type specifies how to match + against the value of the header. + enum: + - Exact + - RegularExpression + - Distinct + type: string + value: + description: |- + Value within the HTTP header. + Do not set this field when Type="Distinct", implying matching on any/all unique + values within the header. + maxLength: 1024 + type: string + required: + - name + type: object + maxItems: 16 + type: array + sourceCIDR: + description: |- + SourceCIDR is the client IP Address range to match on. + At least one of headers or sourceCIDR condition must be specified. + properties: + type: + default: Exact + enum: + - Exact + - Distinct + type: string + value: + description: |- + Value 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`. + maxLength: 256 + minLength: 1 + type: string + required: + - value + type: object + type: object + maxItems: 8 + type: array + cost: + description: |- + Cost specifies the cost of requests and responses for the rule. + + This is optional and if not specified, the default behavior is to reduce the rate limit counters by 1 on + the request path and do not reduce the rate limit counters on the response path. + properties: + request: + description: |- + Request specifies the number to reduce the rate limit counters + on the request path. If this is not specified, the default behavior + is to reduce the rate limit counters by 1. + + When Envoy receives a request that matches the rule, it tries to reduce the + rate limit counters by the specified number. If the counter doesn't have + enough capacity, the request is rate limited. + properties: + from: + description: From specifies where to get the + rate limit cost. Currently, only "Number" + and "Metadata" are supported. + enum: + - Number + - Metadata + type: string + metadata: + description: Metadata specifies the per-request + metadata to retrieve the usage number from. + properties: + key: + description: Key is the key to retrieve + the usage number from the filter metadata. + type: string + namespace: + description: Namespace is the namespace + of the dynamic metadata. + type: string + required: + - key + - namespace + type: object + number: + description: |- + Number specifies the fixed usage number to reduce the rate limit counters. + Using zero can be used to only check the rate limit counters without reducing them. + format: int64 + type: integer + required: + - from + type: object + x-kubernetes-validations: + - message: only one of number or metadata can be + specified + rule: '!(has(self.number) && has(self.metadata))' + response: + description: |- + Response specifies the number to reduce the rate limit counters + after the response is sent back to the client or the request stream is closed. + + The cost is used to reduce the rate limit counters for the matching requests. + Since the reduction happens after the request stream is complete, the rate limit + won't be enforced for the current request, but for the subsequent matching requests. + + This is optional and if not specified, the rate limit counters are not reduced + on the response path. + + Currently, this is only supported for HTTP Global Rate Limits. + properties: + from: + description: From specifies where to get the + rate limit cost. Currently, only "Number" + and "Metadata" are supported. + enum: + - Number + - Metadata + type: string + metadata: + description: Metadata specifies the per-request + metadata to retrieve the usage number from. + properties: + key: + description: Key is the key to retrieve + the usage number from the filter metadata. + type: string + namespace: + description: Namespace is the namespace + of the dynamic metadata. + type: string + required: + - key + - namespace + type: object + number: + description: |- + Number specifies the fixed usage number to reduce the rate limit counters. + Using zero can be used to only check the rate limit counters without reducing them. + format: int64 + type: integer + required: + - from + type: object + x-kubernetes-validations: + - message: only one of number or metadata can be + specified + rule: '!(has(self.number) && has(self.metadata))' + type: object + limit: + description: |- + 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. + properties: + requests: + type: integer + unit: + description: |- + RateLimitUnit specifies the intervals for setting rate limits. + Valid RateLimitUnit values are "Second", "Minute", "Hour", and "Day". + enum: + - Second + - Minute + - Hour + - Day + type: string + required: + - requests + - unit + type: object + required: + - limit + type: object + maxItems: 16 + type: array + x-kubernetes-validations: + - message: response cost is not supported for Local Rate Limits + rule: self.all(foo, !has(foo.cost) || !has(foo.cost.response)) + type: object + type: + description: |- + Type decides the scope for the RateLimits. + Valid RateLimitType values are "Global" or "Local". + enum: + - Global + - Local + type: string + required: + - type + type: object + requestBuffer: + description: |- + RequestBuffer allows the gateway to buffer and fully receive each request from a client before continuing to send the request + upstream to the backends. This can be helpful to shield your backend servers from slow clients, and also to enforce a maximum size per request + as any requests larger than the buffer size will be rejected. + + This can have a negative performance impact so should only be enabled when necessary. + + When enabling this option, you should also configure your connection buffer size to account for these request buffers. There will also be an + increase in memory usage for Envoy that should be accounted for in your deployment settings. + properties: + limit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + Limit specifies the maximum allowed size in bytes for each incoming request buffer. + If exceeded, the request will be rejected with HTTP 413 Content Too Large. + + Accepts values in resource.Quantity format (e.g., "10Mi", "500Ki"). + x-kubernetes-int-or-string: true + type: object + responseOverride: + description: |- + ResponseOverride defines the configuration to override specific responses with a custom one. + If multiple configurations are specified, the first one to match wins. + items: + description: ResponseOverride defines the configuration to override + specific responses with a custom one. + properties: + match: + description: Match configuration. + properties: + statusCodes: + description: Status code to match on. The match evaluates + to true if any of the matches are successful. + items: + description: StatusCodeMatch defines the configuration + for matching a status code. + properties: + range: + description: Range contains the range of status codes. + properties: + end: + description: End of the range, including the end + value. + type: integer + start: + description: Start of the range, including the + start value. + type: integer + required: + - end + - start + type: object + x-kubernetes-validations: + - message: end must be greater than start + rule: self.end > self.start + type: + allOf: + - enum: + - Value + - Range + - enum: + - Value + - Range + default: Value + description: |- + Type is the type of value. + Valid values are Value and Range, default is Value. + type: string + value: + description: Value contains the value of the status + code. + type: integer + required: + - type + type: object + x-kubernetes-validations: + - message: value must be set for type Value + rule: '(!has(self.type) || self.type == ''Value'')? + has(self.value) : true' + - message: range must be set for type Range + rule: '(has(self.type) && self.type == ''Range'')? has(self.range) + : true' + maxItems: 50 + minItems: 1 + type: array + required: + - statusCodes + type: object + response: + description: Response configuration. + properties: + body: + description: Body of the Custom Response + properties: + inline: + description: Inline contains the value as an inline + string. + type: string + type: + allOf: + - enum: + - Inline + - ValueRef + - enum: + - Inline + - ValueRef + default: Inline + description: |- + Type is the type of method to use to read the body value. + Valid values are Inline and ValueRef, default is Inline. + type: string + valueRef: + description: |- + ValueRef contains the contents of the body + specified as a local object reference. + Only a reference to ConfigMap is supported. + + The value of key `response.body` in the ConfigMap will be used as the response body. + If the key is not found, the first value in the ConfigMap will be used. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: inline must be set for type Inline + rule: '(!has(self.type) || self.type == ''Inline'')? has(self.inline) + : true' + - message: valueRef must be set for type ValueRef + rule: '(has(self.type) && self.type == ''ValueRef'')? + has(self.valueRef) : true' + - message: only ConfigMap is supported for ValueRef + rule: 'has(self.valueRef) ? self.valueRef.kind == ''ConfigMap'' + : true' + contentType: + description: Content Type of the response. This will be + set in the Content-Type header. + type: string + statusCode: + description: |- + Status Code of the Custom Response + If unset, does not override the status of response. + type: integer + type: object + required: + - match + - response + type: object + type: array + retry: + description: |- + Retry provides more advanced usage, allowing users to customize the number of retries, retry fallback strategy, and retry triggering conditions. + If not set, retry will be disabled. + properties: + numRetries: + default: 2 + description: NumRetries is the number of retries to be attempted. + Defaults to 2. + format: int32 + minimum: 0 + type: integer + perRetry: + description: PerRetry is the retry policy to be applied per retry + attempt. + properties: + backOff: + description: |- + Backoff is the backoff policy to be applied per retry attempt. gateway uses a fully jittered exponential + back-off algorithm for retries. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#config-http-filters-router-x-envoy-max-retries + properties: + baseInterval: + description: BaseInterval is the base interval between + retries. + format: duration + type: string + maxInterval: + description: |- + MaxInterval is the maximum interval between retries. This parameter is optional, but must be greater than or equal to the base_interval if set. + The default is 10 times the base_interval + format: duration + type: string + type: object + timeout: + description: Timeout is the timeout per retry attempt. + format: duration + type: string + type: object + retryOn: + description: |- + RetryOn specifies the retry trigger condition. + + If not specified, the default is to retry on connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes(503). + properties: + httpStatusCodes: + description: |- + HttpStatusCodes specifies the http status codes to be retried. + The retriable-status-codes trigger must also be configured for these status codes to trigger a retry. + items: + description: HTTPStatus defines the http status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + triggers: + description: Triggers specifies the retry trigger condition(Http/Grpc). + items: + description: TriggerEnum specifies the conditions that trigger + retries. + enum: + - 5xx + - gateway-error + - reset + - connect-failure + - retriable-4xx + - refused-stream + - retriable-status-codes + - cancelled + - deadline-exceeded + - internal + - resource-exhausted + - unavailable + type: string + type: array + type: object + type: object + targetRef: + description: |- + TargetRef is the name of the resource this policy is being attached to. + This policy and the TargetRef MUST be in the same namespace for this + Policy to have effect + + Deprecated: use targetRefs/targetSelectors instead + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + targetRefs: + description: |- + TargetRefs are the names of the Gateway resources this policy + is being attached to. + items: + description: |- + LocalPolicyTargetReferenceWithSectionName identifies an API object to apply a + direct policy to. This should be used as part of Policy resources that can + target single resources. For more information on how this policy attachment + mode works, and a sample Policy resource, refer to the policy attachment + documentation for Gateway API. + + Note: This should only be used for direct policy attachment when references + to SectionName are actually needed. In all other cases, + LocalPolicyTargetReference should be used. + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + type: array + targetSelectors: + description: TargetSelectors allow targeting resources for this policy + based on labels + items: + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group that this selector targets. + Defaults to gateway.networking.k8s.io + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the resource kind that this selector targets. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: MatchLabels are the set of label selectors for + identifying the targeted resource + type: object + required: + - kind + type: object + x-kubernetes-validations: + - message: group must be gateway.networking.k8s.io + rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' + : true ' + type: array + tcpKeepalive: + description: |- + TcpKeepalive settings associated with the upstream client connection. + Disabled by default. + properties: + idleTime: + description: |- + The duration a connection needs to be idle before keep-alive + probes start being sent. + The duration format is + Defaults to `7200s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + The duration between keep-alive probes. + Defaults to `75s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + probes: + description: |- + The total number of unacknowledged probes to send before deciding + the connection is dead. + Defaults to 9. + format: int32 + type: integer + type: object + telemetry: + description: |- + Telemetry configures the telemetry settings for the policy target (Gateway or xRoute). + This will override the telemetry settings in the EnvoyProxy resource. + properties: + tracing: + description: Tracing configures the tracing settings for the backend + or HTTPRoute. + properties: + customTags: + additionalProperties: + properties: + environment: + description: |- + Environment adds value from environment variable to each span. + It's required when the type is "Environment". + properties: + defaultValue: + description: DefaultValue defines the default value + to use if the environment variable is not set. + type: string + name: + description: Name defines the name of the environment + variable which to extract the value from. + type: string + required: + - name + type: object + literal: + description: |- + Literal adds hard-coded value to each span. + It's required when the type is "Literal". + properties: + value: + description: Value defines the hard-coded value + to add to each span. + type: string + required: + - value + type: object + requestHeader: + description: |- + RequestHeader adds value from request header to each span. + It's required when the type is "RequestHeader". + properties: + defaultValue: + description: DefaultValue defines the default value + to use if the request header is not set. + type: string + name: + description: Name defines the name of the request + header which to extract the value from. + type: string + required: + - name + type: object + type: + default: Literal + description: Type defines the type of custom tag. + enum: + - Literal + - Environment + - RequestHeader + type: string + required: + - type + type: object + description: |- + CustomTags defines the custom tags to add to each span. + If provider is kubernetes, pod name and namespace are added by default. + type: object + samplingFraction: + description: |- + SamplingFraction represents the fraction of requests that should be + selected for tracing if no prior sampling decision has been made. + + This will take precedence over sampling fraction on EnvoyProxy if set. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal to denominator + rule: self.numerator <= self.denominator + type: object + type: object + timeout: + description: Timeout settings for the backend connections. + properties: + http: + description: Timeout settings for HTTP. + properties: + connectionIdleTimeout: + description: |- + The idle timeout for an HTTP connection. Idle time is defined as a period in which there are no active requests in the connection. + Default: 1 hour. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + maxConnectionDuration: + description: |- + The maximum duration of an HTTP connection. + Default: unlimited. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + requestTimeout: + description: RequestTimeout is the time until which entire + response is received from the upstream. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + tcp: + description: Timeout settings for TCP. + properties: + connectTimeout: + description: |- + The timeout for network connection establishment, including TCP and TLS handshakes. + Default: 10 seconds. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + type: object + useClientProtocol: + description: |- + UseClientProtocol configures Envoy to prefer sending requests to backends using + the same HTTP protocol that the incoming request used. Defaults to false, which means + that Envoy will use the protocol indicated by the attached BackendRef. + type: boolean + type: object + x-kubernetes-validations: + - message: either targetRef or targetRefs must be used + rule: '(has(self.targetRef) && !has(self.targetRefs)) || (!has(self.targetRef) + && has(self.targetRefs)) || (has(self.targetSelectors) && self.targetSelectors.size() + > 0) ' + - message: this policy can only have a targetRef.group of gateway.networking.k8s.io + rule: 'has(self.targetRef) ? self.targetRef.group == ''gateway.networking.k8s.io'' + : true ' + - message: this policy can only have a targetRef.kind of Gateway/HTTPRoute/GRPCRoute/TCPRoute/UDPRoute/TLSRoute + rule: 'has(self.targetRef) ? self.targetRef.kind in [''Gateway'', ''HTTPRoute'', + ''GRPCRoute'', ''UDPRoute'', ''TCPRoute'', ''TLSRoute''] : true' + - message: this policy does not yet support the sectionName field + rule: 'has(self.targetRef) ? !has(self.targetRef.sectionName) : true' + - message: this policy can only have a targetRefs[*].group of gateway.networking.k8s.io + rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, ref.group == + ''gateway.networking.k8s.io'') : true ' + - message: this policy can only have a targetRefs[*].kind of Gateway/HTTPRoute/GRPCRoute/TCPRoute/UDPRoute/TLSRoute + rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, ref.kind in [''Gateway'', + ''HTTPRoute'', ''GRPCRoute'', ''UDPRoute'', ''TCPRoute'', ''TLSRoute'']) + : true ' + - message: this policy does not yet support the sectionName field + rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, !has(ref.sectionName)) + : true' + status: + description: status defines the current status of BackendTrafficPolicy. + properties: + ancestors: + description: |- + Ancestors is a list of ancestor resources (usually Gateways) that are + associated with the policy, and the status of the policy with respect to + each ancestor. When this policy attaches to a parent, the controller that + manages the parent and the ancestors MUST add an entry to this list when + the controller first sees the policy and SHOULD update the entry as + appropriate when the relevant ancestor is modified. + + Note that choosing the relevant ancestor is left to the Policy designers; + an important part of Policy design is designing the right object level at + which to namespace this status. + + Note also that implementations MUST ONLY populate ancestor status for + the Ancestor resources they are responsible for. Implementations MUST + use the ControllerName field to uniquely identify the entries in this list + that they are responsible for. + + Note that to achieve this, the list of PolicyAncestorStatus structs + MUST be treated as a map with a composite key, made up of the AncestorRef + and ControllerName fields combined. + + A maximum of 16 ancestors will be represented in this list. An empty list + means the Policy is not relevant for any ancestors. + + If this slice is full, implementations MUST NOT add further entries. + Instead they MUST consider the policy unimplementable and signal that + on any related resources such as the ancestor that would be referenced + here. For example, if this list was full on BackendTLSPolicy, no + additional Gateways would be able to reference the Service targeted by + the BackendTLSPolicy. + items: + description: |- + PolicyAncestorStatus describes the status of a route with respect to an + associated Ancestor. + + Ancestors refer to objects that are either the Target of a policy or above it + in terms of object hierarchy. For example, if a policy targets a Service, the + Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and + the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most + useful object to place Policy status on, so we recommend that implementations + SHOULD use Gateway as the PolicyAncestorStatus object unless the designers + have a _very_ good reason otherwise. + + In the context of policy attachment, the Ancestor is used to distinguish which + resource results in a distinct application of this policy. For example, if a policy + targets a Service, it may have a distinct result per attached Gateway. + + Policies targeting the same resource may have different effects depending on the + ancestors of those resources. For example, different Gateways targeting the same + Service may have different capabilities, especially if they have different underlying + implementations. + + For example, in BackendTLSPolicy, the Policy attaches to a Service that is + used as a backend in a HTTPRoute that is itself attached to a Gateway. + In this case, the relevant object for status is the Gateway, and that is the + ancestor object referred to in this status. + + Note that a parent is also an ancestor, so for objects where the parent is the + relevant object for status, this struct SHOULD still be used. + + This struct is intended to be used in a slice that's effectively a map, + with a composite key made up of the AncestorRef and the ControllerName. + properties: + ancestorRef: + description: |- + AncestorRef corresponds with a ParentRef in the spec that this + PolicyAncestorStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: Conditions describes the status of the Policy with + respect to the given Ancestor. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - controllerName + type: object + maxItems: 16 + type: array + required: + - ancestors + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: gateway-crds-helm/templates/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 + name: clienttrafficpolicies.gateway.envoyproxy.io +spec: + group: gateway.envoyproxy.io + names: + categories: + - envoy-gateway + kind: ClientTrafficPolicy + listKind: ClientTrafficPolicyList + plural: clienttrafficpolicies + shortNames: + - ctp + singular: clienttrafficpolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + ClientTrafficPolicy allows the user to configure the behavior of the connection + between the downstream client and Envoy Proxy listener. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of ClientTrafficPolicy. + properties: + clientIPDetection: + description: ClientIPDetectionSettings provides configuration for + determining the original client IP address for requests. + properties: + customHeader: + description: |- + CustomHeader provides configuration for determining the client IP address for a request based on + a trusted custom HTTP header. This uses the custom_header original IP detection extension. + Refer to https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/http/original_ip_detection/custom_header/v3/custom_header.proto + for more details. + properties: + failClosed: + description: |- + FailClosed is a switch used to control the flow of traffic when client IP detection + fails. If set to true, the listener will respond with 403 Forbidden when the client + IP address cannot be determined. + type: boolean + name: + description: Name of the header containing the original downstream + remote address, if present. + maxLength: 255 + minLength: 1 + pattern: ^[A-Za-z0-9-]+$ + type: string + required: + - name + type: object + xForwardedFor: + description: XForwardedForSettings provides configuration for + using X-Forwarded-For headers for determining the client IP + address. + properties: + numTrustedHops: + description: |- + NumTrustedHops controls the number of additional ingress proxy hops from the right side of XFF HTTP + headers to trust when determining the origin client's IP address. + Only one of NumTrustedHops and TrustedCIDRs must be set. + format: int32 + type: integer + trustedCIDRs: + description: |- + TrustedCIDRs is a list of CIDR ranges to trust when evaluating + the remote IP address to determine the original client’s IP address. + When the remote IP address matches a trusted CIDR and the x-forwarded-for header was sent, + each entry in the x-forwarded-for header is evaluated from right to left + and the first public non-trusted address is used as the original client address. + If all addresses in x-forwarded-for are within the trusted list, the first (leftmost) entry is used. + Only one of NumTrustedHops and TrustedCIDRs must be set. + items: + description: |- + CIDR defines a CIDR Address range. + A CIDR can be an IPv4 address range such as "192.168.1.0/24" or an IPv6 address range such as "2001:0db8:11a3:09d7::/64". + pattern: ((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/([0-9]+))|((([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/([0-9]+)) + type: string + minItems: 1 + type: array + type: object + x-kubernetes-validations: + - message: only one of numTrustedHops or trustedCIDRs must be + set + rule: (has(self.numTrustedHops) && !has(self.trustedCIDRs)) + || (!has(self.numTrustedHops) && has(self.trustedCIDRs)) + type: object + x-kubernetes-validations: + - message: customHeader cannot be used in conjunction with xForwardedFor + rule: '!(has(self.xForwardedFor) && has(self.customHeader))' + connection: + description: Connection includes client connection settings. + properties: + bufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + BufferLimit provides configuration for the maximum buffer size in bytes for each incoming connection. + BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space. + For example, 20Mi, 1Gi, 256Ki etc. + Note that when the suffix is not provided, the value is interpreted as bytes. + Default: 32768 bytes. + x-kubernetes-int-or-string: true + connectionLimit: + description: ConnectionLimit defines limits related to connections + properties: + closeDelay: + description: |- + CloseDelay defines the delay to use before closing connections that are rejected + once the limit value is reached. + Default: none. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + value: + description: |- + Value of the maximum concurrent connections limit. + When the limit is reached, incoming connections will be closed after the CloseDelay duration. + format: int64 + minimum: 1 + type: integer + required: + - value + type: object + socketBufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + SocketBufferLimit provides configuration for the maximum buffer size in bytes for each incoming socket. + SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space. + For example, 20Mi, 1Gi, 256Ki etc. + Note that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + type: object + enableProxyProtocol: + description: |- + EnableProxyProtocol interprets the ProxyProtocol header and adds the + Client Address into the X-Forwarded-For header. + Note Proxy Protocol must be present when this field is set, else the connection + is closed. + type: boolean + headers: + description: HeaderSettings provides configuration for header management. + properties: + disableRateLimitHeaders: + description: |- + DisableRateLimitHeaders configures Envoy Proxy to omit the "X-RateLimit-" response headers + when rate limiting is enabled. + type: boolean + earlyRequestHeaders: + description: |- + EarlyRequestHeaders defines settings for early request header modification, before envoy performs + routing, tracing and built-in header manipulation. + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header name and + value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be + matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header name and + value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header to be + matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + enableEnvoyHeaders: + description: |- + EnableEnvoyHeaders configures Envoy Proxy to add the "X-Envoy-" headers to requests + and responses. + type: boolean + preserveXRequestID: + description: |- + PreserveXRequestID configures Envoy to keep the X-Request-ID header if passed for a request that is edge + (Edge request is the request from external clients to front Envoy) and not reset it, which is the current Envoy behaviour. + Defaults to false and cannot be combined with RequestID. + Deprecated: use RequestID=Preserve instead + type: boolean + requestID: + description: |- + RequestID configures Envoy's behavior for handling the `X-Request-ID` header. + Defaults to `Generate` and builds the `X-Request-ID` for every request and ignores pre-existing values from the edge. + (An "edge request" refers to a request from an external client to the Envoy entrypoint.) + enum: + - PreserveOrGenerate + - Preserve + - Generate + - Disable + type: string + withUnderscoresAction: + description: |- + WithUnderscoresAction configures the action to take when an HTTP header with underscores + is encountered. The default action is to reject the request. + enum: + - Allow + - RejectRequest + - DropHeader + type: string + xForwardedClientCert: + description: |- + XForwardedClientCert configures how Envoy Proxy handle the x-forwarded-client-cert (XFCC) HTTP header. + + x-forwarded-client-cert (XFCC) is an HTTP header used to forward the certificate + information of part or all of the clients or proxies that a request has flowed through, + on its way from the client to the server. + + Envoy proxy may choose to sanitize/append/forward the XFCC header before proxying the request. + + If not set, the default behavior is sanitizing the XFCC header. + properties: + certDetailsToAdd: + description: |- + CertDetailsToAdd specifies the fields in the client certificate to be forwarded in the XFCC header. + + Hash(the SHA 256 digest of the current client certificate) and By(the Subject Alternative Name) + are always included if the client certificate is forwarded. + + This field is only applicable when the mode is set to `AppendForward` or + `SanitizeSet` and the client connection is mTLS. + items: + description: XFCCCertData specifies the fields in the client + certificate to be forwarded in the XFCC header. + enum: + - Subject + - Cert + - Chain + - DNS + - URI + type: string + maxItems: 5 + type: array + mode: + description: |- + Mode defines how XFCC header is handled by Envoy Proxy. + If not set, the default mode is `Sanitize`. + enum: + - Sanitize + - ForwardOnly + - AppendForward + - SanitizeSet + - AlwaysForwardOnly + type: string + type: object + x-kubernetes-validations: + - message: certDetailsToAdd can only be set when mode is AppendForward + or SanitizeSet + rule: '(has(self.certDetailsToAdd) && self.certDetailsToAdd.size() + > 0) ? (self.mode == ''AppendForward'' || self.mode == ''SanitizeSet'') + : true' + type: object + x-kubernetes-validations: + - message: preserveXRequestID and requestID cannot both be set. + rule: '!(has(self.preserveXRequestID) && has(self.requestID))' + healthCheck: + description: HealthCheck provides configuration for determining whether + the HTTP/HTTPS listener is healthy. + properties: + path: + description: Path specifies the HTTP path to match on for health + check requests. + maxLength: 1024 + minLength: 1 + type: string + required: + - path + type: object + http1: + description: HTTP1 provides HTTP/1 configuration on the listener. + properties: + enableTrailers: + description: EnableTrailers defines if HTTP/1 trailers should + be proxied by Envoy. + type: boolean + http10: + description: HTTP10 turns on support for HTTP/1.0 and HTTP/0.9 + requests. + properties: + useDefaultHost: + description: |- + UseDefaultHost defines if the HTTP/1.0 request is missing the Host header, + then the hostname associated with the listener should be injected into the + request. + If this is not set and an HTTP/1.0 request arrives without a host, then + it will be rejected. + type: boolean + type: object + preserveHeaderCase: + description: |- + PreserveHeaderCase defines if Envoy should preserve the letter case of headers. + By default, Envoy will lowercase all the headers. + type: boolean + type: object + http2: + description: HTTP2 provides HTTP/2 configuration on the listener. + properties: + initialConnectionWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialConnectionWindowSize sets the initial window size for HTTP/2 connections. + If not set, the default value is 1 MiB. + x-kubernetes-int-or-string: true + initialStreamWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialStreamWindowSize sets the initial window size for HTTP/2 streams. + If not set, the default value is 64 KiB(64*1024). + x-kubernetes-int-or-string: true + maxConcurrentStreams: + description: |- + MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection. + If not set, the default value is 100. + format: int32 + maximum: 2147483647 + minimum: 1 + type: integer + onInvalidMessage: + description: |- + OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error + It's recommended for L2 Envoy deployments to set this value to TerminateStream. + https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two + Default: TerminateConnection + type: string + type: object + http3: + description: HTTP3 provides HTTP/3 configuration on the listener. + type: object + path: + description: Path enables managing how the incoming path set by clients + can be normalized. + properties: + disableMergeSlashes: + description: |- + DisableMergeSlashes allows disabling the default configuration of merging adjacent + slashes in the path. + Note that slash merging is not part of the HTTP spec and is provided for convenience. + type: boolean + escapedSlashesAction: + description: |- + EscapedSlashesAction determines how %2f, %2F, %5c, or %5C sequences in the path URI + should be handled. + The default is UnescapeAndRedirect. + enum: + - KeepUnchanged + - RejectRequest + - UnescapeAndForward + - UnescapeAndRedirect + type: string + type: object + targetRef: + description: |- + TargetRef is the name of the resource this policy is being attached to. + This policy and the TargetRef MUST be in the same namespace for this + Policy to have effect + + Deprecated: use targetRefs/targetSelectors instead + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + targetRefs: + description: |- + TargetRefs are the names of the Gateway resources this policy + is being attached to. + items: + description: |- + LocalPolicyTargetReferenceWithSectionName identifies an API object to apply a + direct policy to. This should be used as part of Policy resources that can + target single resources. For more information on how this policy attachment + mode works, and a sample Policy resource, refer to the policy attachment + documentation for Gateway API. + + Note: This should only be used for direct policy attachment when references + to SectionName are actually needed. In all other cases, + LocalPolicyTargetReference should be used. + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + type: array + targetSelectors: + description: TargetSelectors allow targeting resources for this policy + based on labels + items: + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group that this selector targets. + Defaults to gateway.networking.k8s.io + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the resource kind that this selector targets. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: MatchLabels are the set of label selectors for + identifying the targeted resource + type: object + required: + - kind + type: object + x-kubernetes-validations: + - message: group must be gateway.networking.k8s.io + rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' + : true ' + type: array + tcpKeepalive: + description: |- + TcpKeepalive settings associated with the downstream client connection. + If defined, sets SO_KEEPALIVE on the listener socket to enable TCP Keepalives. + Disabled by default. + properties: + idleTime: + description: |- + The duration a connection needs to be idle before keep-alive + probes start being sent. + The duration format is + Defaults to `7200s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + The duration between keep-alive probes. + Defaults to `75s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + probes: + description: |- + The total number of unacknowledged probes to send before deciding + the connection is dead. + Defaults to 9. + format: int32 + type: integer + type: object + timeout: + description: Timeout settings for the client connections. + properties: + http: + description: Timeout settings for HTTP. + properties: + idleTimeout: + description: |- + IdleTimeout for an HTTP connection. Idle time is defined as a period in which there are no active requests in the connection. + Default: 1 hour. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + requestReceivedTimeout: + description: |- + RequestReceivedTimeout is the duration envoy waits for the complete request reception. This timer starts upon request + initiation and stops when either the last byte of the request is sent upstream or when the response begins. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + tcp: + description: Timeout settings for TCP. + properties: + idleTimeout: + description: |- + IdleTimeout for a TCP connection. Idle time is defined as a period in which there are no + bytes sent or received on either the upstream or downstream connection. + Default: 1 hour. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + type: object + tls: + description: TLS settings configure TLS termination settings with + the downstream client. + properties: + alpnProtocols: + description: |- + ALPNProtocols supplies the list of ALPN protocols that should be + exposed by the listener or used by the proxy to connect to the backend. + Defaults: + 1. HTTPS Routes: h2 and http/1.1 are enabled in listener context. + 2. Other Routes: ALPN is disabled. + 3. Backends: proxy uses the appropriate ALPN options for the backend protocol. + When an empty list is provided, the ALPN TLS extension is disabled. + Supported values are: + - http/1.0 + - http/1.1 + - h2 + items: + description: ALPNProtocol specifies the protocol to be negotiated + using ALPN + enum: + - http/1.0 + - http/1.1 + - h2 + type: string + type: array + ciphers: + description: |- + Ciphers specifies the set of cipher suites supported when + negotiating TLS 1.0 - 1.2. This setting has no effect for TLS 1.3. + In non-FIPS Envoy Proxy builds the default cipher list is: + - [ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305] + - [ECDHE-RSA-AES128-GCM-SHA256|ECDHE-RSA-CHACHA20-POLY1305] + - ECDHE-ECDSA-AES256-GCM-SHA384 + - ECDHE-RSA-AES256-GCM-SHA384 + In builds using BoringSSL FIPS the default cipher list is: + - ECDHE-ECDSA-AES128-GCM-SHA256 + - ECDHE-RSA-AES128-GCM-SHA256 + - ECDHE-ECDSA-AES256-GCM-SHA384 + - ECDHE-RSA-AES256-GCM-SHA384 + items: + type: string + type: array + clientValidation: + description: |- + ClientValidation specifies the configuration to validate the client + initiating the TLS connection to the Gateway listener. + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to + Kubernetes objects that contain TLS certificates of + the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the client. + + A single reference to a Kubernetes ConfigMap or a Kubernetes Secret, + with the CA certificate in a key named `ca.crt` is currently supported. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. + items: + description: |- + SecretObjectReference identifies an API object including its namespace, + defaulting to Secret. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example + "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 8 + type: array + optional: + description: |- + Optional set to true accepts connections even when a client doesn't present a certificate. + Defaults to false, which rejects connections without a valid client certificate. + type: boolean + type: object + ecdhCurves: + description: |- + ECDHCurves specifies the set of supported ECDH curves. + In non-FIPS Envoy Proxy builds the default curves are: + - X25519 + - P-256 + In builds using BoringSSL FIPS the default curve is: + - P-256 + items: + type: string + type: array + maxVersion: + description: |- + Max specifies the maximal TLS protocol version to allow + The default is TLS 1.3 if this is not specified. + enum: + - Auto + - "1.0" + - "1.1" + - "1.2" + - "1.3" + type: string + minVersion: + description: |- + Min specifies the minimal TLS protocol version to allow. + The default is TLS 1.2 if this is not specified. + enum: + - Auto + - "1.0" + - "1.1" + - "1.2" + - "1.3" + type: string + session: + description: Session defines settings related to TLS session management. + properties: + resumption: + description: |- + Resumption determines the proxy's supported TLS session resumption option. + By default, Envoy Gateway does not enable session resumption. Use sessionResumption to + enable stateful and stateless session resumption. Users should consider security impacts + of different resumption methods. Performance gains from resumption are diminished when + Envoy proxy is deployed with more than one replica. + properties: + stateful: + description: Stateful defines setting for stateful (session-id + based) session resumption + type: object + stateless: + description: Stateless defines setting for stateless (session-ticket + based) session resumption + type: object + type: object + type: object + signatureAlgorithms: + description: |- + SignatureAlgorithms specifies which signature algorithms the listener should + support. + items: + type: string + type: array + type: object + x-kubernetes-validations: + - message: setting ciphers has no effect if the minimum possible TLS + version is 1.3 + rule: 'has(self.minVersion) && self.minVersion == ''1.3'' ? !has(self.ciphers) + : true' + - message: minVersion must be smaller or equal to maxVersion + rule: 'has(self.minVersion) && has(self.maxVersion) ? {"Auto":0,"1.0":1,"1.1":2,"1.2":3,"1.3":4}[self.minVersion] + <= {"1.0":1,"1.1":2,"1.2":3,"1.3":4,"Auto":5}[self.maxVersion] + : !has(self.minVersion) && has(self.maxVersion) ? 3 <= {"1.0":1,"1.1":2,"1.2":3,"1.3":4,"Auto":5}[self.maxVersion] + : true' + type: object + x-kubernetes-validations: + - message: either targetRef or targetRefs must be used + rule: '(has(self.targetRef) && !has(self.targetRefs)) || (!has(self.targetRef) + && has(self.targetRefs)) || (has(self.targetSelectors) && self.targetSelectors.size() + > 0) ' + - message: this policy can only have a targetRef.group of gateway.networking.k8s.io + rule: 'has(self.targetRef) ? self.targetRef.group == ''gateway.networking.k8s.io'' + : true' + - message: this policy can only have a targetRef.kind of Gateway + rule: 'has(self.targetRef) ? self.targetRef.kind == ''Gateway'' : true' + - message: this policy can only have a targetRefs[*].group of gateway.networking.k8s.io + rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, ref.group == + ''gateway.networking.k8s.io'') : true' + - message: this policy can only have a targetRefs[*].kind of Gateway + rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, ref.kind == ''Gateway'') + : true' + status: + description: Status defines the current status of ClientTrafficPolicy. + properties: + ancestors: + description: |- + Ancestors is a list of ancestor resources (usually Gateways) that are + associated with the policy, and the status of the policy with respect to + each ancestor. When this policy attaches to a parent, the controller that + manages the parent and the ancestors MUST add an entry to this list when + the controller first sees the policy and SHOULD update the entry as + appropriate when the relevant ancestor is modified. + + Note that choosing the relevant ancestor is left to the Policy designers; + an important part of Policy design is designing the right object level at + which to namespace this status. + + Note also that implementations MUST ONLY populate ancestor status for + the Ancestor resources they are responsible for. Implementations MUST + use the ControllerName field to uniquely identify the entries in this list + that they are responsible for. + + Note that to achieve this, the list of PolicyAncestorStatus structs + MUST be treated as a map with a composite key, made up of the AncestorRef + and ControllerName fields combined. + + A maximum of 16 ancestors will be represented in this list. An empty list + means the Policy is not relevant for any ancestors. + + If this slice is full, implementations MUST NOT add further entries. + Instead they MUST consider the policy unimplementable and signal that + on any related resources such as the ancestor that would be referenced + here. For example, if this list was full on BackendTLSPolicy, no + additional Gateways would be able to reference the Service targeted by + the BackendTLSPolicy. + items: + description: |- + PolicyAncestorStatus describes the status of a route with respect to an + associated Ancestor. + + Ancestors refer to objects that are either the Target of a policy or above it + in terms of object hierarchy. For example, if a policy targets a Service, the + Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and + the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most + useful object to place Policy status on, so we recommend that implementations + SHOULD use Gateway as the PolicyAncestorStatus object unless the designers + have a _very_ good reason otherwise. + + In the context of policy attachment, the Ancestor is used to distinguish which + resource results in a distinct application of this policy. For example, if a policy + targets a Service, it may have a distinct result per attached Gateway. + + Policies targeting the same resource may have different effects depending on the + ancestors of those resources. For example, different Gateways targeting the same + Service may have different capabilities, especially if they have different underlying + implementations. + + For example, in BackendTLSPolicy, the Policy attaches to a Service that is + used as a backend in a HTTPRoute that is itself attached to a Gateway. + In this case, the relevant object for status is the Gateway, and that is the + ancestor object referred to in this status. + + Note that a parent is also an ancestor, so for objects where the parent is the + relevant object for status, this struct SHOULD still be used. + + This struct is intended to be used in a slice that's effectively a map, + with a composite key made up of the AncestorRef and the ControllerName. + properties: + ancestorRef: + description: |- + AncestorRef corresponds with a ParentRef in the spec that this + PolicyAncestorStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: Conditions describes the status of the Policy with + respect to the given Ancestor. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - controllerName + type: object + maxItems: 16 + type: array + required: + - ancestors + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 + name: envoyextensionpolicies.gateway.envoyproxy.io +spec: + group: gateway.envoyproxy.io + names: + kind: EnvoyExtensionPolicy + listKind: EnvoyExtensionPolicyList + plural: envoyextensionpolicies + shortNames: + - eep + singular: envoyextensionpolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: EnvoyExtensionPolicy allows the user to configure various envoy + extensibility options for the Gateway. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of EnvoyExtensionPolicy. + properties: + extProc: + description: |- + ExtProc is an ordered list of external processing filters + that should be added to the envoy filter chain + items: + description: ExtProc defines the configuration for External Processing + filter. + properties: + backendRef: + description: |- + BackendRef references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + + Deprecated: Use BackendRefs instead. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + backendRefs: + description: |- + BackendRefs references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + items: + description: BackendRef defines how an ObjectReference that + is specific to BackendRef. + properties: + fallback: + description: |- + Fallback indicates whether the backend is designated as a fallback. + Multiple fallback backends can be configured. + It is highly recommended to configure active or passive health checks to ensure that failover can be detected + when the active backends become unhealthy and to automatically readjust once the primary backends are healthy again. + The overprovisioning factor is set to 1.4, meaning the fallback backends will only start receiving traffic when + the health of the active backends falls below 72%. + type: boolean + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + backendSettings: + description: |- + BackendSettings holds configuration for managing the connection + to the backend. + properties: + circuitBreaker: + description: |- + Circuit Breaker settings for the upstream connections and requests. + If not set, circuit breakers will be enabled with the default thresholds + properties: + maxConnections: + default: 1024 + description: The maximum number of connections that + Envoy will establish to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRequests: + default: 1024 + description: The maximum number of parallel requests + that Envoy will make to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRetries: + default: 1024 + description: The maximum number of parallel retries + that Envoy will make to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxPendingRequests: + default: 1024 + description: The maximum number of pending requests + that Envoy will queue to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxRequestsPerConnection: + description: |- + The maximum number of requests that Envoy will make over a single connection to the referenced backend defined within a xRoute rule. + Default: unlimited. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + perEndpoint: + description: PerEndpoint defines Circuit Breakers that + will apply per-endpoint for an upstream cluster + properties: + maxConnections: + default: 1024 + description: MaxConnections configures the maximum + number of connections that Envoy will establish + per-endpoint to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + type: object + type: object + connection: + description: Connection includes backend connection settings. + properties: + bufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + BufferLimit Soft limit on size of the cluster’s connections read and write buffers. + BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space. + If unspecified, an implementation defined default is applied (32768 bytes). + For example, 20Mi, 1Gi, 256Ki etc. + Note: that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + socketBufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + SocketBufferLimit provides configuration for the maximum buffer size in bytes for each socket + to backend. + SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space. + For example, 20Mi, 1Gi, 256Ki etc. + Note that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + type: object + dns: + description: DNS includes dns resolution settings. + properties: + dnsRefreshRate: + description: |- + DNSRefreshRate specifies the rate at which DNS records should be refreshed. + Defaults to 30 seconds. + type: string + lookupFamily: + description: |- + LookupFamily determines how Envoy would resolve DNS for Routes where the backend is specified as a fully qualified domain name (FQDN). + If set, this configuration overrides other defaults. + enum: + - IPv4 + - IPv6 + - IPv4Preferred + - IPv6Preferred + - IPv4AndIPv6 + type: string + respectDnsTtl: + description: |- + RespectDNSTTL indicates whether the DNS Time-To-Live (TTL) should be respected. + If the value is set to true, the DNS refresh rate will be set to the resource record’s TTL. + Defaults to true. + type: boolean + type: object + healthCheck: + description: HealthCheck allows gateway to perform active + health checking on backends. + properties: + active: + description: Active health check configuration + properties: + grpc: + description: |- + GRPC defines the configuration of the GRPC health checker. + It's optional, and can only be used if the specified type is GRPC. + properties: + service: + description: |- + Service to send in the health check request. + If this is not specified, then the health check request applies to the entire + server and not to a specific service. + type: string + type: object + healthyThreshold: + default: 1 + description: HealthyThreshold defines the number + of healthy health checks required before a backend + host is marked healthy. + format: int32 + minimum: 1 + type: integer + http: + description: |- + HTTP defines the configuration of http health checker. + It's required while the health checker type is HTTP. + properties: + expectedResponse: + description: ExpectedResponse defines a list + of HTTP expected responses to match. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of the + payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, binary + field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + expectedStatuses: + description: |- + ExpectedStatuses defines a list of HTTP response statuses considered healthy. + Defaults to 200 only + items: + description: HTTPStatus defines the http status + code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + method: + description: |- + Method defines the HTTP method used for health checking. + Defaults to GET + type: string + path: + description: Path defines the HTTP path that + will be requested during health checking. + maxLength: 1024 + minLength: 1 + type: string + required: + - path + type: object + interval: + default: 3s + description: Interval defines the time between active + health checks. + format: duration + type: string + tcp: + description: |- + TCP defines the configuration of tcp health checker. + It's required while the health checker type is TCP. + properties: + receive: + description: Receive defines the expected response + payload. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of the + payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, binary + field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + send: + description: Send defines the request payload. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of the + payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, binary + field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + type: object + timeout: + default: 1s + description: Timeout defines the time to wait for + a health check response. + format: duration + type: string + type: + allOf: + - enum: + - HTTP + - TCP + - GRPC + - enum: + - HTTP + - TCP + - GRPC + description: Type defines the type of health checker. + type: string + unhealthyThreshold: + default: 3 + description: UnhealthyThreshold defines the number + of unhealthy health checks required before a backend + host is marked unhealthy. + format: int32 + minimum: 1 + type: integer + required: + - type + type: object + x-kubernetes-validations: + - message: If Health Checker type is HTTP, http field + needs to be set. + rule: 'self.type == ''HTTP'' ? has(self.http) : !has(self.http)' + - message: If Health Checker type is TCP, tcp field + needs to be set. + rule: 'self.type == ''TCP'' ? has(self.tcp) : !has(self.tcp)' + - message: The grpc field can only be set if the Health + Checker type is GRPC. + rule: 'has(self.grpc) ? self.type == ''GRPC'' : true' + panicThreshold: + description: |- + When number of unhealthy endpoints for a backend reaches this threshold + Envoy will disregard health status and balance across all endpoints. + It's designed to prevent a situation in which host failures cascade throughout the cluster + as load increases. If not set, the default value is 50%. To disable panic mode, set value to `0`. + format: int32 + maximum: 100 + minimum: 0 + type: integer + passive: + description: Passive passive check configuration + properties: + baseEjectionTime: + default: 30s + description: BaseEjectionTime defines the base duration + for which a host will be ejected on consecutive + failures. + format: duration + type: string + consecutive5XxErrors: + default: 5 + description: Consecutive5xxErrors sets the number + of consecutive 5xx errors triggering ejection. + format: int32 + type: integer + consecutiveGatewayErrors: + default: 0 + description: ConsecutiveGatewayErrors sets the number + of consecutive gateway errors triggering ejection. + format: int32 + type: integer + consecutiveLocalOriginFailures: + default: 5 + description: |- + ConsecutiveLocalOriginFailures sets the number of consecutive local origin failures triggering ejection. + Parameter takes effect only when split_external_local_origin_errors is set to true. + format: int32 + type: integer + interval: + default: 3s + description: Interval defines the time between passive + health checks. + format: duration + type: string + maxEjectionPercent: + default: 10 + description: MaxEjectionPercent sets the maximum + percentage of hosts in a cluster that can be ejected. + format: int32 + type: integer + splitExternalLocalOriginErrors: + default: false + description: SplitExternalLocalOriginErrors enables + splitting of errors between external and local + origin. + type: boolean + type: object + type: object + http2: + description: HTTP2 provides HTTP/2 configuration for backend + connections. + properties: + initialConnectionWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialConnectionWindowSize sets the initial window size for HTTP/2 connections. + If not set, the default value is 1 MiB. + x-kubernetes-int-or-string: true + initialStreamWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialStreamWindowSize sets the initial window size for HTTP/2 streams. + If not set, the default value is 64 KiB(64*1024). + x-kubernetes-int-or-string: true + maxConcurrentStreams: + description: |- + MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection. + If not set, the default value is 100. + format: int32 + maximum: 2147483647 + minimum: 1 + type: integer + onInvalidMessage: + description: |- + OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error + It's recommended for L2 Envoy deployments to set this value to TerminateStream. + https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two + Default: TerminateConnection + type: string + type: object + loadBalancer: + description: |- + LoadBalancer policy to apply when routing traffic from the gateway to + the backend endpoints. Defaults to `LeastRequest`. + properties: + consistentHash: + description: |- + ConsistentHash defines the configuration when the load balancer type is + set to ConsistentHash + properties: + cookie: + description: Cookie configures the cookie hash policy + when the consistent hash type is set to Cookie. + properties: + attributes: + additionalProperties: + type: string + description: Additional Attributes to set for + the generated cookie. + type: object + name: + description: |- + Name of the cookie to hash. + If this cookie does not exist in the request, Envoy will generate a cookie and set + the TTL on the response back to the client based on Layer 4 + attributes of the backend endpoint, to ensure that these future requests + go to the same backend endpoint. Make sure to set the TTL field for this case. + type: string + ttl: + description: |- + TTL of the generated cookie if the cookie is not present. This value sets the + Max-Age attribute value. + type: string + required: + - name + type: object + header: + description: Header configures the header hash policy + when the consistent hash type is set to Header. + properties: + name: + description: Name of the header to hash. + type: string + required: + - name + type: object + tableSize: + default: 65537 + description: The table size for consistent hashing, + must be prime number limited to 5000011. + format: int64 + maximum: 5000011 + minimum: 2 + type: integer + type: + description: |- + ConsistentHashType defines the type of input to hash on. Valid Type values are + "SourceIP", + "Header", + "Cookie". + enum: + - SourceIP + - Header + - Cookie + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If consistent hash type is header, the header + field must be set. + rule: 'self.type == ''Header'' ? has(self.header) + : !has(self.header)' + - message: If consistent hash type is cookie, the cookie + field must be set. + rule: 'self.type == ''Cookie'' ? has(self.cookie) + : !has(self.cookie)' + slowStart: + description: |- + SlowStart defines the configuration related to the slow start load balancer policy. + If set, during slow start window, traffic sent to the newly added hosts will gradually increase. + Currently this is only supported for RoundRobin and LeastRequest load balancers + properties: + window: + description: |- + Window defines the duration of the warm up period for newly added host. + During slow start window, traffic sent to the newly added hosts will gradually increase. + Currently only supports linear growth of traffic. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#config-cluster-v3-cluster-slowstartconfig + type: string + required: + - window + type: object + type: + description: |- + Type decides the type of Load Balancer policy. + Valid LoadBalancerType values are + "ConsistentHash", + "LeastRequest", + "Random", + "RoundRobin". + enum: + - ConsistentHash + - LeastRequest + - Random + - RoundRobin + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If LoadBalancer type is consistentHash, consistentHash + field needs to be set. + rule: 'self.type == ''ConsistentHash'' ? has(self.consistentHash) + : !has(self.consistentHash)' + - message: Currently SlowStart is only supported for RoundRobin + and LeastRequest load balancers. + rule: 'self.type in [''Random'', ''ConsistentHash''] ? + !has(self.slowStart) : true ' + proxyProtocol: + description: ProxyProtocol enables the Proxy Protocol when + communicating with the backend. + properties: + version: + description: |- + Version of ProxyProtol + Valid ProxyProtocolVersion values are + "V1" + "V2" + enum: + - V1 + - V2 + type: string + required: + - version + type: object + retry: + description: |- + Retry provides more advanced usage, allowing users to customize the number of retries, retry fallback strategy, and retry triggering conditions. + If not set, retry will be disabled. + properties: + numRetries: + default: 2 + description: NumRetries is the number of retries to + be attempted. Defaults to 2. + format: int32 + minimum: 0 + type: integer + perRetry: + description: PerRetry is the retry policy to be applied + per retry attempt. + properties: + backOff: + description: |- + Backoff is the backoff policy to be applied per retry attempt. gateway uses a fully jittered exponential + back-off algorithm for retries. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#config-http-filters-router-x-envoy-max-retries + properties: + baseInterval: + description: BaseInterval is the base interval + between retries. + format: duration + type: string + maxInterval: + description: |- + MaxInterval is the maximum interval between retries. This parameter is optional, but must be greater than or equal to the base_interval if set. + The default is 10 times the base_interval + format: duration + type: string + type: object + timeout: + description: Timeout is the timeout per retry attempt. + format: duration + type: string + type: object + retryOn: + description: |- + RetryOn specifies the retry trigger condition. + + If not specified, the default is to retry on connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes(503). + properties: + httpStatusCodes: + description: |- + HttpStatusCodes specifies the http status codes to be retried. + The retriable-status-codes trigger must also be configured for these status codes to trigger a retry. + items: + description: HTTPStatus defines the http status + code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + triggers: + description: Triggers specifies the retry trigger + condition(Http/Grpc). + items: + description: TriggerEnum specifies the conditions + that trigger retries. + enum: + - 5xx + - gateway-error + - reset + - connect-failure + - retriable-4xx + - refused-stream + - retriable-status-codes + - cancelled + - deadline-exceeded + - internal + - resource-exhausted + - unavailable + type: string + type: array + type: object + type: object + tcpKeepalive: + description: |- + TcpKeepalive settings associated with the upstream client connection. + Disabled by default. + properties: + idleTime: + description: |- + The duration a connection needs to be idle before keep-alive + probes start being sent. + The duration format is + Defaults to `7200s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + The duration between keep-alive probes. + Defaults to `75s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + probes: + description: |- + The total number of unacknowledged probes to send before deciding + the connection is dead. + Defaults to 9. + format: int32 + type: integer + type: object + timeout: + description: Timeout settings for the backend connections. + properties: + http: + description: Timeout settings for HTTP. + properties: + connectionIdleTimeout: + description: |- + The idle timeout for an HTTP connection. Idle time is defined as a period in which there are no active requests in the connection. + Default: 1 hour. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + maxConnectionDuration: + description: |- + The maximum duration of an HTTP connection. + Default: unlimited. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + requestTimeout: + description: RequestTimeout is the time until which + entire response is received from the upstream. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + tcp: + description: Timeout settings for TCP. + properties: + connectTimeout: + description: |- + The timeout for network connection establishment, including TCP and TLS handshakes. + Default: 10 seconds. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + type: object + type: object + failOpen: + description: |- + FailOpen defines if requests or responses that cannot be processed due to connectivity to the + external processor are terminated or passed-through. + Default: false + type: boolean + messageTimeout: + description: |- + MessageTimeout is the timeout for a response to be returned from the external processor + Default: 200ms + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + metadata: + description: |- + Metadata defines options related to the sending and receiving of dynamic metadata. + These options define which metadata namespaces would be sent to the processor and which dynamic metadata + namespaces the processor would be permitted to emit metadata to. + Users can specify custom namespaces or well-known envoy metadata namespace (such as envoy.filters.http.ext_authz) + documented here: https://www.envoyproxy.io/docs/envoy/latest/configuration/advanced/well_known_dynamic_metadata#well-known-dynamic-metadata + Default: no metadata context is sent or received from the external processor + properties: + accessibleNamespaces: + description: AccessibleNamespaces are metadata namespaces + that are sent to the external processor as context + items: + type: string + type: array + writableNamespaces: + description: WritableNamespaces are metadata namespaces + that the external processor can write to + items: + type: string + maxItems: 8 + type: array + x-kubernetes-validations: + - message: writableNamespaces cannot contain well-known + Envoy HTTP filter namespaces + rule: self.all(f, !f.startsWith('envoy.filters.http')) + type: object + processingMode: + description: |- + ProcessingMode defines how request and response body is processed + Default: header and body are not sent to the external processor + properties: + allowModeOverride: + description: |- + AllowModeOverride allows the external processor to override the processing mode set via the + `mode_override` field in the gRPC response message. This defaults to false. + type: boolean + request: + description: |- + Defines processing mode for requests. If present, request headers are sent. Request body is processed according + to the specified mode. + properties: + attributes: + description: |- + Defines which attributes are sent to the external processor. Envoy Gateway currently + supports only the following attribute prefixes: connection, source, destination, + request, response, upstream and xds.route. + https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes + items: + pattern: ^(connection\.|source\.|destination\.|request\.|response\.|upstream\.|xds\.route_)[a-z_1-9]*$ + type: string + type: array + body: + description: Defines body processing mode + enum: + - Streamed + - Buffered + - BufferedPartial + type: string + type: object + response: + description: |- + Defines processing mode for responses. If present, response headers are sent. Response body is processed according + to the specified mode. + properties: + attributes: + description: |- + Defines which attributes are sent to the external processor. Envoy Gateway currently + supports only the following attribute prefixes: connection, source, destination, + request, response, upstream and xds.route. + https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes + items: + pattern: ^(connection\.|source\.|destination\.|request\.|response\.|upstream\.|xds\.route_)[a-z_1-9]*$ + type: string + type: array + body: + description: Defines body processing mode + enum: + - Streamed + - Buffered + - BufferedPartial + type: string + type: object + type: object + type: object + x-kubernetes-validations: + - message: BackendRefs must be used, backendRef is not supported. + rule: '!has(self.backendRef)' + - message: BackendRefs only supports Service and Backend kind. + rule: 'has(self.backendRefs) ? self.backendRefs.all(f, f.kind + == ''Service'' || f.kind == ''Backend'') : true' + - message: BackendRefs only supports Core and gateway.envoyproxy.io + group. + rule: 'has(self.backendRefs) ? (self.backendRefs.all(f, f.group + == "" || f.group == ''gateway.envoyproxy.io'')) : true' + maxItems: 16 + type: array + lua: + description: |- + Lua is an ordered list of Lua filters + that should be added to the envoy filter chain + items: + description: |- + Lua defines a Lua extension + Only one of Inline or ValueRef must be set + properties: + inline: + description: Inline contains the source code as an inline string. + type: string + type: + default: Inline + description: |- + Type is the type of method to use to read the Lua value. + Valid values are Inline and ValueRef, default is Inline. + enum: + - Inline + - ValueRef + type: string + valueRef: + description: |- + ValueRef has the source code specified as a local object reference. + Only a reference to ConfigMap is supported. + The value of key `lua` in the ConfigMap will be used. + If the key is not found, the first value in the ConfigMap will be used. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" + or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + x-kubernetes-validations: + - message: Only a reference to an object of kind ConfigMap belonging + to default v1 API group is supported. + rule: self.kind == 'ConfigMap' && (self.group == 'v1' || self.group + == '') + required: + - type + type: object + x-kubernetes-validations: + - message: Exactly one of inline or valueRef must be set with correct + type. + rule: (self.type == 'Inline' && has(self.inline) && !has(self.valueRef)) + || (self.type == 'ValueRef' && !has(self.inline) && has(self.valueRef)) + maxItems: 16 + type: array + targetRef: + description: |- + TargetRef is the name of the resource this policy is being attached to. + This policy and the TargetRef MUST be in the same namespace for this + Policy to have effect + + Deprecated: use targetRefs/targetSelectors instead + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + targetRefs: + description: |- + TargetRefs are the names of the Gateway resources this policy + is being attached to. + items: + description: |- + LocalPolicyTargetReferenceWithSectionName identifies an API object to apply a + direct policy to. This should be used as part of Policy resources that can + target single resources. For more information on how this policy attachment + mode works, and a sample Policy resource, refer to the policy attachment + documentation for Gateway API. + + Note: This should only be used for direct policy attachment when references + to SectionName are actually needed. In all other cases, + LocalPolicyTargetReference should be used. + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + type: array + targetSelectors: + description: TargetSelectors allow targeting resources for this policy + based on labels + items: + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group that this selector targets. + Defaults to gateway.networking.k8s.io + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the resource kind that this selector targets. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: MatchLabels are the set of label selectors for + identifying the targeted resource + type: object + required: + - kind + type: object + x-kubernetes-validations: + - message: group must be gateway.networking.k8s.io + rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' + : true ' + type: array + wasm: + description: |- + Wasm is a list of Wasm extensions to be loaded by the Gateway. + Order matters, as the extensions will be loaded in the order they are + defined in this list. + items: + description: |- + Wasm defines a Wasm extension. + + Note: at the moment, Envoy Gateway does not support configuring Wasm runtime. + v8 is used as the VM runtime for the Wasm extensions. + properties: + code: + description: Code is the Wasm code for the extension. + properties: + http: + description: |- + HTTP is the HTTP URL containing the Wasm code. + + Note that the HTTP server must be accessible from the Envoy proxy. + properties: + sha256: + description: |- + SHA256 checksum that will be used to verify the Wasm code. + + If not specified, Envoy Gateway will not verify the downloaded Wasm code. + kubebuilder:validation:Pattern=`^[a-f0-9]{64}$` + type: string + tls: + description: TLS configuration when connecting to the + Wasm code source. + properties: + caCertificateRef: + description: |- + CACertificateRef contains a references to + Kubernetes objects that contain TLS certificates of + the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the Wasm code source. + + Kubernetes ConfigMap and Kubernetes Secret are supported. + Note: The ConfigMap or Secret must be in the same namespace as the EnvoyExtensionPolicy. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For + example "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + required: + - caCertificateRef + type: object + url: + description: URL is the URL containing the Wasm code. + pattern: ^((https?:)(\/\/\/?)([\w]*(?::[\w]*)?@)?([\d\w\.-]+)(?::(\d+))?)?([\/\\\w\.()-]*)?(?:([?][^#]*)?(#.*)?)* + type: string + required: + - url + type: object + image: + description: |- + Image is the OCI image containing the Wasm code. + + Note that the image must be accessible from the Envoy Gateway. + properties: + pullSecretRef: + description: |- + PullSecretRef is a reference to the secret containing the credentials to pull the image. + Only support Kubernetes Secret resource from the same namespace. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example + "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + x-kubernetes-validations: + - message: only support Secret kind. + rule: self.kind == 'Secret' + sha256: + description: |- + SHA256 checksum that will be used to verify the OCI image. + + It must match the digest of the OCI image. + + If not specified, Envoy Gateway will not verify the downloaded OCI image. + kubebuilder:validation:Pattern=`^[a-f0-9]{64}$` + type: string + tls: + description: TLS configuration when connecting to the + Wasm code source. + properties: + caCertificateRef: + description: |- + CACertificateRef contains a references to + Kubernetes objects that contain TLS certificates of + the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the Wasm code source. + + Kubernetes ConfigMap and Kubernetes Secret are supported. + Note: The ConfigMap or Secret must be in the same namespace as the EnvoyExtensionPolicy. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For + example "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + required: + - caCertificateRef + type: object + url: + description: |- + URL is the URL of the OCI image. + URL can be in the format of `registry/image:tag` or `registry/image@sha256:digest`. + type: string + required: + - url + type: object + pullPolicy: + description: |- + PullPolicy is the policy to use when pulling the Wasm module by either the HTTP or Image source. + This field is only applicable when the SHA256 field is not set. + + If not specified, the default policy is IfNotPresent except for OCI images whose tag is latest. + + Note: EG does not update the Wasm module every time an Envoy proxy requests + the Wasm module even if the pull policy is set to Always. + It only updates the Wasm module when the EnvoyExtension resource version changes. + enum: + - IfNotPresent + - Always + type: string + type: + allOf: + - enum: + - HTTP + - Image + - enum: + - HTTP + - Image + - ConfigMap + description: |- + Type is the type of the source of the Wasm code. + Valid WasmCodeSourceType values are "HTTP" or "Image". + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If type is HTTP, http field needs to be set. + rule: 'self.type == ''HTTP'' ? has(self.http) : !has(self.http)' + - message: If type is Image, image field needs to be set. + rule: 'self.type == ''Image'' ? has(self.image) : !has(self.image)' + config: + description: |- + Config is the configuration for the Wasm extension. + This configuration will be passed as a JSON string to the Wasm extension. + x-kubernetes-preserve-unknown-fields: true + env: + description: Env configures the environment for the Wasm extension + properties: + hostKeys: + description: |- + HostKeys is a list of keys for environment variables from the host envoy process + that should be passed into the Wasm VM. This is useful for passing secrets to to Wasm extensions. + items: + type: string + type: array + type: object + failOpen: + default: false + description: |- + FailOpen is a switch used to control the behavior when a fatal error occurs + during the initialization or the execution of the Wasm extension. + If FailOpen is set to true, the system bypasses the Wasm extension and + allows the traffic to pass through. Otherwise, if it is set to false or + not set (defaulting to false), the system blocks the traffic and returns + an HTTP 5xx error. + type: boolean + name: + description: |- + Name is a unique name for this Wasm extension. It is used to identify the + Wasm extension if multiple extensions are handled by the same vm_id and root_id. + It's also used for logging/debugging. + If not specified, EG will generate a unique name for the Wasm extension. + type: string + rootID: + description: |- + RootID is a unique ID for a set of extensions in a VM which will share a + RootContext and Contexts if applicable (e.g., an Wasm HttpFilter and an Wasm AccessLog). + If left blank, all extensions with a blank root_id with the same vm_id will share Context(s). + + Note: RootID must match the root_id parameter used to register the Context in the Wasm code. + type: string + required: + - code + type: object + maxItems: 16 + type: array + type: object + x-kubernetes-validations: + - message: either targetRef or targetRefs must be used + rule: '(has(self.targetRef) && !has(self.targetRefs)) || (!has(self.targetRef) + && has(self.targetRefs)) || (has(self.targetSelectors) && self.targetSelectors.size() + > 0) ' + - message: this policy can only have a targetRef.group of gateway.networking.k8s.io + rule: 'has(self.targetRef) ? self.targetRef.group == ''gateway.networking.k8s.io'' + : true' + - message: this policy can only have a targetRef.kind of Gateway/HTTPRoute/GRPCRoute/TCPRoute/UDPRoute/TLSRoute + rule: 'has(self.targetRef) ? self.targetRef.kind in [''Gateway'', ''HTTPRoute'', + ''GRPCRoute'', ''UDPRoute'', ''TCPRoute'', ''TLSRoute''] : true' + - message: this policy does not yet support the sectionName field + rule: 'has(self.targetRef) ? !has(self.targetRef.sectionName) : true' + - message: this policy can only have a targetRefs[*].group of gateway.networking.k8s.io + rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, ref.group == + ''gateway.networking.k8s.io'') : true ' + - message: this policy can only have a targetRefs[*].kind of Gateway/HTTPRoute/GRPCRoute/TCPRoute/UDPRoute/TLSRoute + rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, ref.kind in [''Gateway'', + ''HTTPRoute'', ''GRPCRoute'', ''UDPRoute'', ''TCPRoute'', ''TLSRoute'']) + : true ' + - message: this policy does not yet support the sectionName field + rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, !has(ref.sectionName)) + : true' + status: + description: Status defines the current status of EnvoyExtensionPolicy. + properties: + ancestors: + description: |- + Ancestors is a list of ancestor resources (usually Gateways) that are + associated with the policy, and the status of the policy with respect to + each ancestor. When this policy attaches to a parent, the controller that + manages the parent and the ancestors MUST add an entry to this list when + the controller first sees the policy and SHOULD update the entry as + appropriate when the relevant ancestor is modified. + + Note that choosing the relevant ancestor is left to the Policy designers; + an important part of Policy design is designing the right object level at + which to namespace this status. + + Note also that implementations MUST ONLY populate ancestor status for + the Ancestor resources they are responsible for. Implementations MUST + use the ControllerName field to uniquely identify the entries in this list + that they are responsible for. + + Note that to achieve this, the list of PolicyAncestorStatus structs + MUST be treated as a map with a composite key, made up of the AncestorRef + and ControllerName fields combined. + + A maximum of 16 ancestors will be represented in this list. An empty list + means the Policy is not relevant for any ancestors. + + If this slice is full, implementations MUST NOT add further entries. + Instead they MUST consider the policy unimplementable and signal that + on any related resources such as the ancestor that would be referenced + here. For example, if this list was full on BackendTLSPolicy, no + additional Gateways would be able to reference the Service targeted by + the BackendTLSPolicy. + items: + description: |- + PolicyAncestorStatus describes the status of a route with respect to an + associated Ancestor. + + Ancestors refer to objects that are either the Target of a policy or above it + in terms of object hierarchy. For example, if a policy targets a Service, the + Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and + the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most + useful object to place Policy status on, so we recommend that implementations + SHOULD use Gateway as the PolicyAncestorStatus object unless the designers + have a _very_ good reason otherwise. + + In the context of policy attachment, the Ancestor is used to distinguish which + resource results in a distinct application of this policy. For example, if a policy + targets a Service, it may have a distinct result per attached Gateway. + + Policies targeting the same resource may have different effects depending on the + ancestors of those resources. For example, different Gateways targeting the same + Service may have different capabilities, especially if they have different underlying + implementations. + + For example, in BackendTLSPolicy, the Policy attaches to a Service that is + used as a backend in a HTTPRoute that is itself attached to a Gateway. + In this case, the relevant object for status is the Gateway, and that is the + ancestor object referred to in this status. + + Note that a parent is also an ancestor, so for objects where the parent is the + relevant object for status, this struct SHOULD still be used. + + This struct is intended to be used in a slice that's effectively a map, + with a composite key made up of the AncestorRef and the ControllerName. + properties: + ancestorRef: + description: |- + AncestorRef corresponds with a ParentRef in the spec that this + PolicyAncestorStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: Conditions describes the status of the Policy with + respect to the given Ancestor. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - controllerName + type: object + maxItems: 16 + type: array + required: + - ancestors + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoypatchpolicies.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 + name: envoypatchpolicies.gateway.envoyproxy.io +spec: + group: gateway.envoyproxy.io + names: + categories: + - envoy-gateway + kind: EnvoyPatchPolicy + listKind: EnvoyPatchPolicyList + plural: envoypatchpolicies + shortNames: + - epp + singular: envoypatchpolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Programmed")].reason + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + EnvoyPatchPolicy allows the user to modify the generated Envoy xDS + resources by Envoy Gateway using this patch API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of EnvoyPatchPolicy. + properties: + jsonPatches: + description: JSONPatch defines the JSONPatch configuration. + items: + description: |- + EnvoyJSONPatchConfig defines the configuration for patching a Envoy xDS Resource + using JSONPatch semantic + properties: + name: + description: Name is the name of the resource + type: string + operation: + description: Patch defines the JSON Patch Operation + properties: + from: + description: |- + From is the source location of the value to be copied or moved. Only valid + for move or copy operations + Refer to https://datatracker.ietf.org/doc/html/rfc6901 for more details. + type: string + jsonPath: + description: |- + JSONPath is a JSONPath expression. Refer to https://datatracker.ietf.org/doc/rfc9535/ for more details. + It produces one or more JSONPointer expressions based on the given JSON document. + If no JSONPointer is found, it will result in an error. + If the 'Path' property is also set, it will be appended to the resulting JSONPointer expressions from the JSONPath evaluation. + This is useful when creating a property that does not yet exist in the JSON document. + The final JSONPointer expressions specifies the locations in the target document/field where the operation will be applied. + type: string + op: + description: Op is the type of operation to perform + enum: + - add + - remove + - replace + - move + - copy + - test + type: string + path: + description: |- + Path is a JSONPointer expression. Refer to https://datatracker.ietf.org/doc/html/rfc6901 for more details. + It specifies the location of the target document/field where the operation will be performed + type: string + value: + description: |- + Value is the new value of the path location. The value is only used by + the `add` and `replace` operations. + x-kubernetes-preserve-unknown-fields: true + required: + - op + type: object + type: + description: Type is the typed URL of the Envoy xDS Resource + enum: + - type.googleapis.com/envoy.config.listener.v3.Listener + - type.googleapis.com/envoy.config.route.v3.RouteConfiguration + - type.googleapis.com/envoy.config.cluster.v3.Cluster + - type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment + - type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret + type: string + required: + - name + - operation + - type + type: object + type: array + priority: + description: |- + Priority of the EnvoyPatchPolicy. + If multiple EnvoyPatchPolicies are applied to the same + TargetRef, they will be applied in the ascending order of + the priority i.e. int32.min has the highest priority and + int32.max has the lowest priority. + Defaults to 0. + format: int32 + type: integer + targetRef: + description: |- + TargetRef is the name of the Gateway API resource this policy + is being attached to. + By default, attaching to Gateway is supported and + when mergeGateways is enabled it should attach to GatewayClass. + This Policy and the TargetRef MUST be in the same namespace + for this Policy to have effect and be applied to the Gateway + TargetRef + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + type: + description: |- + Type decides the type of patch. + Valid EnvoyPatchType values are "JSONPatch". + enum: + - JSONPatch + type: string + required: + - targetRef + - type + type: object + status: + description: Status defines the current status of EnvoyPatchPolicy. + properties: + ancestors: + description: |- + Ancestors is a list of ancestor resources (usually Gateways) that are + associated with the policy, and the status of the policy with respect to + each ancestor. When this policy attaches to a parent, the controller that + manages the parent and the ancestors MUST add an entry to this list when + the controller first sees the policy and SHOULD update the entry as + appropriate when the relevant ancestor is modified. + + Note that choosing the relevant ancestor is left to the Policy designers; + an important part of Policy design is designing the right object level at + which to namespace this status. + + Note also that implementations MUST ONLY populate ancestor status for + the Ancestor resources they are responsible for. Implementations MUST + use the ControllerName field to uniquely identify the entries in this list + that they are responsible for. + + Note that to achieve this, the list of PolicyAncestorStatus structs + MUST be treated as a map with a composite key, made up of the AncestorRef + and ControllerName fields combined. + + A maximum of 16 ancestors will be represented in this list. An empty list + means the Policy is not relevant for any ancestors. + + If this slice is full, implementations MUST NOT add further entries. + Instead they MUST consider the policy unimplementable and signal that + on any related resources such as the ancestor that would be referenced + here. For example, if this list was full on BackendTLSPolicy, no + additional Gateways would be able to reference the Service targeted by + the BackendTLSPolicy. + items: + description: |- + PolicyAncestorStatus describes the status of a route with respect to an + associated Ancestor. + + Ancestors refer to objects that are either the Target of a policy or above it + in terms of object hierarchy. For example, if a policy targets a Service, the + Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and + the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most + useful object to place Policy status on, so we recommend that implementations + SHOULD use Gateway as the PolicyAncestorStatus object unless the designers + have a _very_ good reason otherwise. + + In the context of policy attachment, the Ancestor is used to distinguish which + resource results in a distinct application of this policy. For example, if a policy + targets a Service, it may have a distinct result per attached Gateway. + + Policies targeting the same resource may have different effects depending on the + ancestors of those resources. For example, different Gateways targeting the same + Service may have different capabilities, especially if they have different underlying + implementations. + + For example, in BackendTLSPolicy, the Policy attaches to a Service that is + used as a backend in a HTTPRoute that is itself attached to a Gateway. + In this case, the relevant object for status is the Gateway, and that is the + ancestor object referred to in this status. + + Note that a parent is also an ancestor, so for objects where the parent is the + relevant object for status, this struct SHOULD still be used. + + This struct is intended to be used in a slice that's effectively a map, + with a composite key made up of the AncestorRef and the ControllerName. + properties: + ancestorRef: + description: |- + AncestorRef corresponds with a ParentRef in the spec that this + PolicyAncestorStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: Conditions describes the status of the Policy with + respect to the given Ancestor. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - controllerName + type: object + maxItems: 16 + type: array + required: + - ancestors + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 + name: envoyproxies.gateway.envoyproxy.io +spec: + group: gateway.envoyproxy.io + names: + categories: + - envoy-gateway + kind: EnvoyProxy + listKind: EnvoyProxyList + plural: envoyproxies + shortNames: + - eproxy + singular: envoyproxy + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: EnvoyProxy is the schema for the envoyproxies API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: EnvoyProxySpec defines the desired state of EnvoyProxy. + properties: + backendTLS: + description: |- + BackendTLS is the TLS configuration for the Envoy proxy to use when connecting to backends. + These settings are applied on backends for which TLS policies are specified. + properties: + alpnProtocols: + description: |- + ALPNProtocols supplies the list of ALPN protocols that should be + exposed by the listener or used by the proxy to connect to the backend. + Defaults: + 1. HTTPS Routes: h2 and http/1.1 are enabled in listener context. + 2. Other Routes: ALPN is disabled. + 3. Backends: proxy uses the appropriate ALPN options for the backend protocol. + When an empty list is provided, the ALPN TLS extension is disabled. + Supported values are: + - http/1.0 + - http/1.1 + - h2 + items: + description: ALPNProtocol specifies the protocol to be negotiated + using ALPN + enum: + - http/1.0 + - http/1.1 + - h2 + type: string + type: array + ciphers: + description: |- + Ciphers specifies the set of cipher suites supported when + negotiating TLS 1.0 - 1.2. This setting has no effect for TLS 1.3. + In non-FIPS Envoy Proxy builds the default cipher list is: + - [ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305] + - [ECDHE-RSA-AES128-GCM-SHA256|ECDHE-RSA-CHACHA20-POLY1305] + - ECDHE-ECDSA-AES256-GCM-SHA384 + - ECDHE-RSA-AES256-GCM-SHA384 + In builds using BoringSSL FIPS the default cipher list is: + - ECDHE-ECDSA-AES128-GCM-SHA256 + - ECDHE-RSA-AES128-GCM-SHA256 + - ECDHE-ECDSA-AES256-GCM-SHA384 + - ECDHE-RSA-AES256-GCM-SHA384 + items: + type: string + type: array + clientCertificateRef: + description: |- + ClientCertificateRef defines the reference to a Kubernetes Secret that contains + the client certificate and private key for Envoy to use when connecting to + backend services and external services, such as ExtAuth, ALS, OpenTelemetry, etc. + This secret should be located within the same namespace as the Envoy proxy resource that references it. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + ecdhCurves: + description: |- + ECDHCurves specifies the set of supported ECDH curves. + In non-FIPS Envoy Proxy builds the default curves are: + - X25519 + - P-256 + In builds using BoringSSL FIPS the default curve is: + - P-256 + items: + type: string + type: array + maxVersion: + description: |- + Max specifies the maximal TLS protocol version to allow + The default is TLS 1.3 if this is not specified. + enum: + - Auto + - "1.0" + - "1.1" + - "1.2" + - "1.3" + type: string + minVersion: + description: |- + Min specifies the minimal TLS protocol version to allow. + The default is TLS 1.2 if this is not specified. + enum: + - Auto + - "1.0" + - "1.1" + - "1.2" + - "1.3" + type: string + signatureAlgorithms: + description: |- + SignatureAlgorithms specifies which signature algorithms the listener should + support. + items: + type: string + type: array + type: object + x-kubernetes-validations: + - message: setting ciphers has no effect if the minimum possible TLS + version is 1.3 + rule: 'has(self.minVersion) && self.minVersion == ''1.3'' ? !has(self.ciphers) + : true' + - message: minVersion must be smaller or equal to maxVersion + rule: 'has(self.minVersion) && has(self.maxVersion) ? {"Auto":0,"1.0":1,"1.1":2,"1.2":3,"1.3":4}[self.minVersion] + <= {"1.0":1,"1.1":2,"1.2":3,"1.3":4,"Auto":5}[self.maxVersion] + : !has(self.minVersion) && has(self.maxVersion) ? 3 <= {"1.0":1,"1.1":2,"1.2":3,"1.3":4,"Auto":5}[self.maxVersion] + : true' + bootstrap: + description: |- + 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. + properties: + jsonPatches: + description: |- + JSONPatches is an array of JSONPatches to be applied to the default bootstrap. Patches are + applied in the order in which they are defined. + items: + description: |- + JSONPatchOperation defines the JSON Patch Operation as defined in + https://datatracker.ietf.org/doc/html/rfc6902 + properties: + from: + description: |- + From is the source location of the value to be copied or moved. Only valid + for move or copy operations + Refer to https://datatracker.ietf.org/doc/html/rfc6901 for more details. + type: string + jsonPath: + description: |- + JSONPath is a JSONPath expression. Refer to https://datatracker.ietf.org/doc/rfc9535/ for more details. + It produces one or more JSONPointer expressions based on the given JSON document. + If no JSONPointer is found, it will result in an error. + If the 'Path' property is also set, it will be appended to the resulting JSONPointer expressions from the JSONPath evaluation. + This is useful when creating a property that does not yet exist in the JSON document. + The final JSONPointer expressions specifies the locations in the target document/field where the operation will be applied. + type: string + op: + description: Op is the type of operation to perform + enum: + - add + - remove + - replace + - move + - copy + - test + type: string + path: + description: |- + Path is a JSONPointer expression. Refer to https://datatracker.ietf.org/doc/html/rfc6901 for more details. + It specifies the location of the target document/field where the operation will be performed + type: string + value: + description: |- + Value is the new value of the path location. The value is only used by + the `add` and `replace` operations. + x-kubernetes-preserve-unknown-fields: true + required: + - op + type: object + type: array + type: + default: Replace + description: |- + Type is the type of the bootstrap configuration, it should be either **Replace**, **Merge**, or **JSONPatch**. + If unspecified, it defaults to Replace. + enum: + - Merge + - Replace + - JSONPatch + type: string + value: + description: Value is a YAML string of the bootstrap. + type: string + type: object + x-kubernetes-validations: + - message: provided bootstrap patch doesn't match the configured patch + type + rule: 'self.type == ''JSONPatch'' ? self.jsonPatches.size() > 0 + : has(self.value)' + concurrency: + description: |- + Concurrency defines the number of worker threads to run. If unset, it defaults to + the number of cpuset threads on the platform. + format: int32 + type: integer + extraArgs: + description: |- + ExtraArgs defines additional command line options that are provided to Envoy. + More info: https://www.envoyproxy.io/docs/envoy/latest/operations/cli#command-line-options + Note: some command line options are used internally(e.g. --log-level) so they cannot be provided here. + items: + type: string + type: array + filterOrder: + description: |- + FilterOrder defines the order of filters in the Envoy proxy's HTTP filter chain. + The FilterPosition in the list will be applied in the order they are defined. + If unspecified, the default filter order is applied. + Default filter order is: + + - envoy.filters.http.health_check + + - envoy.filters.http.fault + + - envoy.filters.http.cors + + - envoy.filters.http.ext_authz + + - envoy.filters.http.basic_auth + + - envoy.filters.http.oauth2 + + - envoy.filters.http.jwt_authn + + - envoy.filters.http.stateful_session + + - envoy.filters.http.lua + + - envoy.filters.http.ext_proc + + - envoy.filters.http.wasm + + - envoy.filters.http.rbac + + - envoy.filters.http.local_ratelimit + + - envoy.filters.http.ratelimit + + - envoy.filters.http.custom_response + + - envoy.filters.http.router + + Note: "envoy.filters.http.router" cannot be reordered, it's always the last filter in the chain. + items: + description: FilterPosition defines the position of an Envoy HTTP + filter in the filter chain. + properties: + after: + description: |- + After defines the filter that should come after the filter. + Only one of Before or After must be set. + enum: + - envoy.filters.http.health_check + - envoy.filters.http.fault + - envoy.filters.http.cors + - envoy.filters.http.ext_authz + - envoy.filters.http.api_key_auth + - envoy.filters.http.basic_auth + - envoy.filters.http.oauth2 + - envoy.filters.http.jwt_authn + - envoy.filters.http.stateful_session + - envoy.filters.http.lua + - envoy.filters.http.ext_proc + - envoy.filters.http.wasm + - envoy.filters.http.rbac + - envoy.filters.http.local_ratelimit + - envoy.filters.http.ratelimit + - envoy.filters.http.custom_response + - envoy.filters.http.compressor + type: string + before: + description: |- + Before defines the filter that should come before the filter. + Only one of Before or After must be set. + enum: + - envoy.filters.http.health_check + - envoy.filters.http.fault + - envoy.filters.http.cors + - envoy.filters.http.ext_authz + - envoy.filters.http.api_key_auth + - envoy.filters.http.basic_auth + - envoy.filters.http.oauth2 + - envoy.filters.http.jwt_authn + - envoy.filters.http.stateful_session + - envoy.filters.http.lua + - envoy.filters.http.ext_proc + - envoy.filters.http.wasm + - envoy.filters.http.rbac + - envoy.filters.http.local_ratelimit + - envoy.filters.http.ratelimit + - envoy.filters.http.custom_response + - envoy.filters.http.compressor + type: string + name: + description: Name of the filter. + enum: + - envoy.filters.http.health_check + - envoy.filters.http.fault + - envoy.filters.http.cors + - envoy.filters.http.ext_authz + - envoy.filters.http.api_key_auth + - envoy.filters.http.basic_auth + - envoy.filters.http.oauth2 + - envoy.filters.http.jwt_authn + - envoy.filters.http.stateful_session + - envoy.filters.http.lua + - envoy.filters.http.ext_proc + - envoy.filters.http.wasm + - envoy.filters.http.rbac + - envoy.filters.http.local_ratelimit + - envoy.filters.http.ratelimit + - envoy.filters.http.custom_response + - envoy.filters.http.compressor + type: string + required: + - name + type: object + x-kubernetes-validations: + - message: one of before or after must be specified + rule: (has(self.before) || has(self.after)) + - message: only one of before or after can be specified + rule: (has(self.before) && !has(self.after)) || (!has(self.before) + && has(self.after)) + type: array + ipFamily: + description: |- + IPFamily specifies the IP family for the EnvoyProxy fleet. + This setting only affects the Gateway listener port and does not impact + other aspects of the Envoy proxy configuration. + If not specified, the system will operate as follows: + - It defaults to IPv4 only. + - IPv6 and dual-stack environments are not supported in this default configuration. + Note: To enable IPv6 or dual-stack functionality, explicit configuration is required. + enum: + - IPv4 + - IPv6 + - DualStack + type: string + logging: + default: + level: + default: warn + description: Logging defines logging parameters for managed proxies. + properties: + level: + additionalProperties: + description: LogLevel defines a log level for Envoy Gateway + and EnvoyProxy system logs. + enum: + - trace + - debug + - info + - warn + - error + type: string + default: + default: warn + description: |- + 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 "default: warn". + type: object + type: object + mergeGateways: + description: |- + MergeGateways defines if Gateway resources should be merged onto the same Envoy Proxy Infrastructure. + Setting this field to true would merge all Gateway Listeners under the parent Gateway Class. + This means that the port, protocol and hostname tuple must be unique for every listener. + If a duplicate listener is detected, the newer listener (based on timestamp) will be rejected and its status will be updated with a "Accepted=False" condition. + type: boolean + preserveRouteOrder: + description: |- + PreserveRouteOrder determines if the order of matching for HTTPRoutes is determined by Gateway-API + specification (https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteRule) + or preserves the order defined by users in the HTTPRoute's HTTPRouteRule list. + Default: False + type: boolean + provider: + description: |- + Provider defines the desired resource provider and provider-specific configuration. + If unspecified, the "Kubernetes" resource provider is used with default configuration + parameters. + properties: + kubernetes: + description: |- + 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. + properties: + envoyDaemonSet: + description: |- + EnvoyDaemonSet defines the desired state of the Envoy daemonset resource. + Disabled by default, a deployment resource is used instead to provision the Envoy Proxy fleet + properties: + container: + description: Container defines the desired specification + of main container. + properties: + env: + description: List of environment variables to set + in the container. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. + Must be a C_IDENTIFIER. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: |- + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + properties: + apiVersion: + description: Version of the schema the + FieldPath is written in terms of, + defaults to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + properties: + containerName: + description: 'Container name: required + for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults + to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to + select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in + the pod's namespace + properties: + key: + description: The key of the secret to + select from. Must be a valid secret + key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + image: + description: Image specifies the EnvoyProxy container + image to be used, instead of the default image. + type: string + resources: + description: |- + Resources required by this container. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry + in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: |- + 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/ + properties: + allowPrivilegeEscalation: + description: |- + AllowPrivilegeEscalation controls whether a process can gain more + privileges than its parent process. This bool directly controls if + the no_new_privs flag will be set on the container process. + AllowPrivilegeEscalation is true always when the container is: + 1) run as Privileged + 2) has CAP_SYS_ADMIN + Note that this field cannot be set when spec.os.name is windows. + type: boolean + appArmorProfile: + description: |- + appArmorProfile is the AppArmor options to use by this container. If set, this profile + overrides the pod's appArmorProfile. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile loaded on the node that should be used. + The profile must be preconfigured on the node to work. + Must match the loaded name of the profile. + Must be set if and only if type is "Localhost". + type: string + type: + description: |- + type indicates which kind of AppArmor profile will be applied. + Valid options are: + Localhost - a profile pre-loaded on the node. + RuntimeDefault - the container runtime's default profile. + Unconfined - no AppArmor enforcement. + type: string + required: + - type + type: object + capabilities: + description: |- + The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the container runtime. + Note that this field cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX + capabilities type + type: string + type: array + x-kubernetes-list-type: atomic + drop: + description: Removed capabilities + items: + description: Capability represent POSIX + capabilities type + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + description: |- + Run container in privileged mode. + Processes in privileged containers are essentially equivalent to root on the host. + Defaults to false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: |- + procMount denotes the type of proc mount to use for the containers. + The default value is Default which uses the container runtime defaults for + readonly paths and masked paths. + This requires the ProcMountType feature flag to be enabled. + Note that this field cannot be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: |- + Whether this container has a read-only root filesystem. + Default is false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label + that applies to the container. + type: string + role: + description: Role is a SELinux role label + that applies to the container. + type: string + type: + description: Type is a SELinux type label + that applies to the container. + type: string + user: + description: User is a SELinux user label + that applies to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by this container. If seccomp options are + provided at both the pod & container level, the container options + override the pod options. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + windowsOptions: + description: |- + The Windows specific settings applied to all containers. + If unspecified, the options from the PodSecurityContext will be used. + If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: |- + GMSACredentialSpec is where the GMSA admission webhook + (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the + GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the + name of the GMSA credential spec to use. + type: string + hostProcess: + description: |- + HostProcess determines if a container should be run as a 'Host Process' container. + All of a Pod's containers must have the same effective HostProcess value + (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). + In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: |- + The UserName in Windows to run the entrypoint of the container process. + Defaults to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + volumeMounts: + description: |- + VolumeMounts are volumes to mount into the container's filesystem. + Cannot be updated. + items: + description: VolumeMount describes a mounting of + a Volume within a container. + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + mountPropagation determines how mounts are propagated from the host + to container and the other way around. + When not set, MountPropagationNone is used. + This field is beta in 1.10. + When RecursiveReadOnly is set to IfPossible or to Enabled, MountPropagation must be None or unspecified + (which defaults to None). + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + recursiveReadOnly: + description: |- + RecursiveReadOnly specifies whether read-only mounts should be handled + recursively. + + If ReadOnly is false, this field has no meaning and must be unspecified. + + If ReadOnly is true, and this field is set to Disabled, the mount is not made + recursively read-only. If this field is set to IfPossible, the mount is made + recursively read-only, if it is supported by the container runtime. If this + field is set to Enabled, the mount is made recursively read-only if it is + supported by the container runtime, otherwise the pod will not be started and + an error will be generated to indicate the reason. + + If this field is set to IfPossible or Enabled, MountPropagation must be set to + None (or be unspecified, which defaults to None). + + If this field is not specified, it is treated as an equivalent of Disabled. + type: string + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: |- + Expanded path within the volume from which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + Defaults to "" (volume's root). + SubPathExpr and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + type: object + name: + description: |- + Name of the daemonSet. + When unset, this defaults to an autogenerated name. + type: string + patch: + description: Patch defines how to perform the patch operation + to daemonset + properties: + type: + description: |- + Type is the type of merge operation to perform + + By default, StrategicMerge is used as the patch type. + type: string + value: + description: Object contains the raw configuration + for merged object + x-kubernetes-preserve-unknown-fields: true + required: + - value + type: object + pod: + description: Pod defines the desired specification of + pod. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + 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. + If it's null, this PodAffinityTerm matches with no 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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. + If it's null, this PodAffinityTerm matches with no 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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. + If it's null, this PodAffinityTerm matches with no 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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. + If it's null, this PodAffinityTerm matches with no 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + type: object + type: object + annotations: + additionalProperties: + type: string + description: |- + Annotations are the annotations that should be appended to the pods. + By default, no pod annotations are appended. + type: object + imagePullSecrets: + description: |- + ImagePullSecrets is an optional list of references to secrets + in the same namespace to use for pulling any of the images used by this PodSpec. + If specified, these secrets will be passed to individual puller implementations for them to use. + More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + labels: + additionalProperties: + type: string + description: |- + Labels are the additional labels that should be tagged to the pods. + By default, no additional pod labels are tagged. + type: object + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is a selector which must be true for the pod to fit on a node. + Selector which must match a node's labels for the pod to be scheduled on that node. + More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + type: object + securityContext: + description: |- + SecurityContext holds pod-level security attributes and common container settings. + Optional: Defaults to empty. See type description for default values of each field. + properties: + appArmorProfile: + description: |- + appArmorProfile is the AppArmor options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile loaded on the node that should be used. + The profile must be preconfigured on the node to work. + Must match the loaded name of the profile. + Must be set if and only if type is "Localhost". + type: string + type: + description: |- + type indicates which kind of AppArmor profile will be applied. + Valid options are: + Localhost - a profile pre-loaded on the node. + RuntimeDefault - the container runtime's default profile. + Unconfined - no AppArmor enforcement. + type: string + required: + - type + type: object + fsGroup: + description: |- + A special supplemental group that applies to all containers in a pod. + Some volume types allow the Kubelet to change the ownership of that volume + to be owned by the pod: + + 1. The owning GID will be the FSGroup + 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + 3. The permission bits are OR'd with rw-rw---- + + If unset, the Kubelet will not modify the ownership and permissions of any volume. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + fsGroupChangePolicy: + description: |- + fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + before being exposed inside Pod. This field will only apply to + volume types which support fsGroup based ownership(and permissions). + It will have no effect on ephemeral volume types such as: secret, configmaps + and emptydir. + Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + Note that this field cannot be set when spec.os.name is windows. + type: string + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxChangePolicy: + description: |- + seLinuxChangePolicy defines how the container's SELinux label is applied to all volumes used by the Pod. + It has no effect on nodes that do not support SELinux or to volumes does not support SELinux. + Valid values are "MountOption" and "Recursive". + + "Recursive" means relabeling of all files on all Pod volumes by the container runtime. + This may be slow for large volumes, but allows mixing privileged and unprivileged Pods sharing the same volume on the same node. + + "MountOption" mounts all eligible Pod volumes with `-o context` mount option. + This requires all Pods that share the same volume to use the same SELinux label. + It is not possible to share the same volume among privileged and unprivileged Pods. + Eligible volumes are in-tree FibreChannel and iSCSI volumes, and all CSI volumes + whose CSI driver announces SELinux support by setting spec.seLinuxMount: true in their + CSIDriver instance. Other volumes are always re-labelled recursively. + "MountOption" value is allowed only when SELinuxMount feature gate is enabled. + + If not specified and SELinuxMount feature gate is enabled, "MountOption" is used. + If not specified and SELinuxMount feature gate is disabled, "MountOption" is used for ReadWriteOncePod volumes + and "Recursive" for all other volumes. + + This field affects only Pods that have SELinux label set, either in PodSecurityContext or in SecurityContext of all containers. + + All Pods that use the same volume should use the same seLinuxChangePolicy, otherwise some pods can get stuck in ContainerCreating state. + Note that this field cannot be set when spec.os.name is windows. + type: string + seLinuxOptions: + description: |- + The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in SecurityContext. If set in + both SecurityContext and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label + that applies to the container. + type: string + role: + description: Role is a SELinux role label + that applies to the container. + type: string + type: + description: Type is a SELinux type label + that applies to the container. + type: string + user: + description: User is a SELinux user label + that applies to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + supplementalGroups: + description: |- + A list of groups applied to the first process run in each container, in + addition to the container's primary GID and fsGroup (if specified). If + the SupplementalGroupsPolicy feature is enabled, the + supplementalGroupsPolicy field determines whether these are in addition + to or instead of any group memberships defined in the container image. + If unspecified, no additional groups are added, though group memberships + defined in the container image may still be used, depending on the + supplementalGroupsPolicy field. + Note that this field cannot be set when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + description: |- + Defines how supplemental groups of the first container processes are calculated. + Valid values are "Merge" and "Strict". If not specified, "Merge" is used. + (Alpha) Using the field requires the SupplementalGroupsPolicy feature gate to be enabled + and the container runtime must implement support for this feature. + Note that this field cannot be set when spec.os.name is windows. + type: string + sysctls: + description: |- + Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + sysctls (by the container runtime) might fail to launch. + Note that this field cannot be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter + to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: + description: |- + The Windows specific settings applied to all containers. + If unspecified, the options within a container's SecurityContext will be used. + If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: |- + GMSACredentialSpec is where the GMSA admission webhook + (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the + GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the + name of the GMSA credential spec to use. + type: string + hostProcess: + description: |- + HostProcess determines if a container should be run as a 'Host Process' container. + All of a Pod's containers must have the same effective HostProcess value + (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). + In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: |- + The UserName in Windows to run the entrypoint of the container process. + Defaults to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + 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 + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: TopologySpreadConstraint specifies + how to spread matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + volumes: + description: |- + Volumes that can be mounted by containers belonging to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes + items: + description: Volume represents a named volume in + a pod that may be accessed by any container in + the pod. + properties: + awsElasticBlockStore: + description: |- + awsElasticBlockStore represents an AWS Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + Deprecated: AWSElasticBlockStore is deprecated. All operations for the in-tree + awsElasticBlockStore type are redirected to the ebs.csi.aws.com CSI driver. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + format: int32 + type: integer + readOnly: + description: |- + readOnly value true will force the readOnly setting in VolumeMounts. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: boolean + volumeID: + description: |- + volumeID is unique ID of the persistent disk resource in AWS (Amazon EBS volume). + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + required: + - volumeID + type: object + azureDisk: + description: |- + azureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. + Deprecated: AzureDisk is deprecated. All operations for the in-tree azureDisk type + are redirected to the disk.csi.azure.com CSI driver. + properties: + cachingMode: + description: 'cachingMode is the Host Caching + mode: None, Read Only, Read Write.' + type: string + diskName: + description: diskName is the Name of the + data disk in the blob storage + type: string + diskURI: + description: diskURI is the URI of data + disk in the blob storage + type: string + fsType: + default: ext4 + description: |- + fsType is Filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + kind: + description: 'kind expected values are Shared: + multiple blob disks per storage account Dedicated: + single blob disk per storage account Managed: + azure managed data disk (only in managed + availability set). defaults to shared' + type: string + readOnly: + default: false + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: |- + azureFile represents an Azure File Service mount on the host and bind mount to the pod. + Deprecated: AzureFile is deprecated. All operations for the in-tree azureFile type + are redirected to the file.csi.azure.com CSI driver. + properties: + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: secretName is the name of + secret that contains Azure Storage Account + Name and Key + type: string + shareName: + description: shareName is the azure share + Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: |- + cephFS represents a Ceph FS mount on the host that shares a pod's lifetime. + Deprecated: CephFS is deprecated and the in-tree cephfs type is no longer supported. + properties: + monitors: + description: |- + monitors is Required: Monitors is a collection of Ceph monitors + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + items: + type: string + type: array + x-kubernetes-list-type: atomic + path: + description: 'path is Optional: Used as + the mounted root, rather than the full + Ceph tree, default is /' + type: string + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: boolean + secretFile: + description: |- + secretFile is Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + secretRef: + description: |- + secretRef is Optional: SecretRef is reference to the authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is optional: User is the rados user name, default is admin + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + required: + - monitors + type: object + cinder: + description: |- + cinder represents a cinder volume attached and mounted on kubelets host machine. + Deprecated: Cinder is deprecated. All operations for the in-tree cinder type + are redirected to the cinder.csi.openstack.org CSI driver. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: boolean + secretRef: + description: |- + secretRef is optional: points to a secret object containing parameters used to connect + to OpenStack. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + description: |- + volumeID used to identify the volume in cinder. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + required: + - volumeID + type: object + configMap: + description: configMap represents a configMap + that should populate this volume + properties: + defaultMode: + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional specify whether the + ConfigMap or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + description: csi (Container Storage Interface) + represents ephemeral storage that is handled + by certain external CSI drivers. + properties: + driver: + description: |- + driver is the name of the CSI driver that handles this volume. + Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: |- + fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the associated CSI driver + which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: |- + nodePublishSecretRef is a reference to the secret object containing + sensitive information to pass to the CSI driver to complete the CSI + NodePublishVolume and NodeUnpublishVolume calls. + This field is optional, and may be empty if no secret is required. If the + secret object contains more than one secret, all secret references are passed. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + description: |- + readOnly specifies a read-only configuration for the volume. + Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: |- + volumeAttributes stores driver-specific properties that are passed to the CSI + driver. Consult your driver's documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: downwardAPI represents downward + API about the pod that should populate this + volume + properties: + defaultMode: + description: |- + Optional: mode bits to use on created files by default. Must be a + Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: Items is a list of downward + API volume file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects a + field of the pod: only annotations, + labels, name, namespace and uid + are supported.' + properties: + apiVersion: + description: Version of the schema + the FieldPath is written in + terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field + to select in the specified API + version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file to + be created. Must not be absolute + or contain the ''..'' path. Must + be utf-8 encoded. The first item + of the relative path must not start + with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: + required for volumes, optional + for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output + format of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + emptyDir: + description: |- + emptyDir represents a temporary directory that shares a pod's lifetime. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + properties: + medium: + description: |- + medium represents what type of storage medium should back this directory. + The default is "" which means to use the node's default medium. + Must be an empty string (default) or Memory. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: |- + sizeLimit is the total amount of local storage required for this EmptyDir volume. + The size limit is also applicable for memory medium. + The maximum usage on memory medium EmptyDir would be the minimum value between + the SizeLimit specified here and the sum of memory limits of all containers in a pod. + The default is nil which means that the limit is undefined. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + description: |- + ephemeral represents a volume that is handled by a cluster storage driver. + The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, + and deleted when the pod is removed. + + Use this if: + a) the volume is only needed while the pod runs, + b) features of normal volumes like restoring from snapshot or capacity + tracking are needed, + c) the storage driver is specified through a storage class, and + d) the storage driver supports dynamic volume provisioning through + a PersistentVolumeClaim (see EphemeralVolumeSource for more + information on the connection between this volume type + and PersistentVolumeClaim). + + Use PersistentVolumeClaim or one of the vendor-specific + APIs for volumes that persist for longer than the lifecycle + of an individual pod. + + Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to + be used that way - see the documentation of the driver for + more information. + + A pod can use both types of ephemeral volumes and + persistent volumes at the same time. + properties: + volumeClaimTemplate: + description: |- + Will be used to create a stand-alone PVC to provision the volume. + The pod in which this EphemeralVolumeSource is embedded will be the + owner of the PVC, i.e. the PVC will be deleted together with the + pod. The name of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` array + entry. Pod validation will reject the pod if the concatenated name + is not valid for a PVC (for example, too long). + + An existing PVC with that name that is not owned by the pod + will *not* be used for the pod to avoid using an unrelated + volume by mistake. Starting the pod is then blocked until + the unrelated PVC is removed. If such a pre-created PVC is + meant to be used by the pod, the PVC has to updated with an + owner reference to the pod once the pod exists. Normally + this should not be necessary, but it may be useful when + manually reconstructing a broken cluster. + + This field is read-only and no changes will be made by Kubernetes + to the PVC after it has been created. + + Required, must not be nil. + properties: + metadata: + description: |- + May contain labels and annotations that will be copied into the PVC + when creating it. No other fields are allowed and will be rejected during + validation. + type: object + spec: + description: |- + The specification for the PersistentVolumeClaim. The entire content is + copied unchanged into the PVC that gets created from this + template. The same fields as in a PersistentVolumeClaim + are also valid here. + properties: + accessModes: + description: |- + accessModes contains the desired access modes the volume should have. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 + items: + type: string + type: array + x-kubernetes-list-type: atomic + dataSource: + description: |- + dataSource field can be used to specify either: + * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) + If the provisioner or an external controller can support the specified data source, + it will create a new volume based on the contents of the specified data source. + When the AnyVolumeDataSource feature gate is enabled, dataSource contents will be copied to dataSourceRef, + and dataSourceRef contents will be copied to dataSource when dataSourceRef.namespace is not specified. + If the namespace is specified, then dataSourceRef will not be copied to dataSource. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type + of resource being referenced + type: string + name: + description: Name is the name + of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + description: |- + dataSourceRef specifies the object from which to populate the volume with data, if a non-empty + volume is desired. This may be any object from a non-empty API group (non + core object) or a PersistentVolumeClaim object. + When this field is specified, volume binding will only succeed if the type of + the specified object matches some installed volume populator or dynamic + provisioner. + This field will replace the functionality of the dataSource field and as such + if both fields are non-empty, they must have the same value. For backwards + compatibility, when namespace isn't specified in dataSourceRef, + both fields (dataSource and dataSourceRef) will be set to the same + value automatically if one of them is empty and the other is non-empty. + When namespace is specified in dataSourceRef, + dataSource isn't set to the same value and must be empty. + There are three important differences between dataSource and dataSourceRef: + * While dataSource only allows two specific types of objects, dataSourceRef + allows any non-core object, as well as PersistentVolumeClaim objects. + * While dataSource ignores disallowed values (dropping them), dataSourceRef + preserves all values, and generates an error if a disallowed value is + specified. + * While dataSource only allows local objects, dataSourceRef allows objects + in any namespaces. + (Beta) Using this field requires the AnyVolumeDataSource feature gate to be enabled. + (Alpha) Using the namespace field of dataSourceRef requires the CrossNamespaceVolumeDataSource feature gate to be enabled. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type + of resource being referenced + type: string + name: + description: Name is the name + of resource being referenced + type: string + namespace: + description: |- + Namespace is the namespace of resource being referenced + Note that when a namespace is specified, a gateway.networking.k8s.io/ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. + (Alpha) This field requires the CrossNamespaceVolumeDataSource feature gate to be enabled. + type: string + required: + - kind + - name + type: object + resources: + description: |- + resources represents the minimum resources the volume should have. + If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements + that are lower than previous value but must still be higher than capacity recorded in the + status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + selector: + description: selector is a label + query over volumes to consider + for binding. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + storageClassName: + description: |- + storageClassName is the name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 + type: string + volumeAttributesClassName: + description: |- + volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim. + If specified, the CSI driver will create or update the volume with the attributes defined + in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName, + it can be changed after the claim is created. An empty string value means that no VolumeAttributesClass + will be applied to the claim but it's not allowed to reset this field to empty string once it is set. + If unspecified and the PersistentVolumeClaim is unbound, the default VolumeAttributesClass + will be set by the persistentvolume controller if it exists. + If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be + set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource + exists. + More info: https://kubernetes.io/docs/concepts/storage/volume-attributes-classes/ + (Beta) Using this field requires the VolumeAttributesClass feature gate to be enabled (off by default). + type: string + volumeMode: + description: |- + volumeMode defines what type of volume is required by the claim. + Value of Filesystem is implied when not included in claim spec. + type: string + volumeName: + description: volumeName is the binding + reference to the PersistentVolume + backing this claim. + type: string + type: object + required: + - spec + type: object + type: object + fc: + description: fc represents a Fibre Channel resource + that is attached to a kubelet's host machine + and then exposed to the pod. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + lun: + description: 'lun is Optional: FC target + lun number' + format: int32 + type: integer + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + targetWWNs: + description: 'targetWWNs is Optional: FC + target worldwide names (WWNs)' + items: + type: string + type: array + x-kubernetes-list-type: atomic + wwids: + description: |- + wwids Optional: FC volume world wide identifiers (wwids) + Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + flexVolume: + description: |- + flexVolume represents a generic volume resource that is + provisioned/attached using an exec based plugin. + Deprecated: FlexVolume is deprecated. Consider using a CSIDriver instead. + properties: + driver: + description: driver is the name of the driver + to use for this volume. + type: string + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + type: string + options: + additionalProperties: + type: string + description: 'options is Optional: this + field holds extra command options if any.' + type: object + readOnly: + description: |- + readOnly is Optional: defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef is Optional: secretRef is reference to the secret object containing + sensitive information to pass to the plugin scripts. This may be + empty if no secret object is specified. If the secret object + contains more than one secret, all secrets are passed to the plugin + scripts. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + description: |- + flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running. + Deprecated: Flocker is deprecated and the in-tree flocker type is no longer supported. + properties: + datasetName: + description: |- + datasetName is Name of the dataset stored as metadata -> name on the dataset for Flocker + should be considered as deprecated + type: string + datasetUUID: + description: datasetUUID is the UUID of + the dataset. This is unique identifier + of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: |- + gcePersistentDisk represents a GCE Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + Deprecated: GCEPersistentDisk is deprecated. All operations for the in-tree + gcePersistentDisk type are redirected to the pd.csi.storage.gke.io CSI driver. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + properties: + fsType: + description: |- + fsType is filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + format: int32 + type: integer + pdName: + description: |- + pdName is unique name of the PD resource in GCE. Used to identify the disk in GCE. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: boolean + required: + - pdName + type: object + gitRepo: + description: |- + gitRepo represents a git repository at a particular revision. + Deprecated: GitRepo is deprecated. To provision a container with a git repo, mount an + EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir + into the Pod's container. + properties: + directory: + description: |- + directory is the target directory name. + Must not contain or start with '..'. If '.' is supplied, the volume directory will be the + git repository. Otherwise, if specified, the volume will contain the git repository in + the subdirectory with the given name. + type: string + repository: + description: repository is the URL + type: string + revision: + description: revision is the commit hash + for the specified revision. + type: string + required: + - repository + type: object + glusterfs: + description: |- + glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. + Deprecated: Glusterfs is deprecated and the in-tree glusterfs type is no longer supported. + More info: https://examples.k8s.io/volumes/glusterfs/README.md + properties: + endpoints: + description: |- + endpoints is the endpoint name that details Glusterfs topology. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + path: + description: |- + path is the Glusterfs volume path. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + readOnly: + description: |- + readOnly here will force the Glusterfs volume to be mounted with read-only permissions. + Defaults to false. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: |- + hostPath represents a pre-existing file or directory on the host + machine that is directly exposed to the container. This is generally + used for system agents or other privileged things that are allowed + to see the host machine. Most containers will NOT need this. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + properties: + path: + description: |- + path of the directory on the host. + If the path is a symlink, it will follow the link to the real path. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + type: + description: |- + type for HostPath Volume + Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + required: + - path + type: object + image: + description: |- + image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. + The volume is resolved at pod startup depending on which PullPolicy value is provided: + + - Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. + - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. + - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. + + The volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. + A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. + The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. + The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. + The volume will be mounted read-only (ro) and non-executable files (noexec). + Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. + The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type. + properties: + pullPolicy: + description: |- + Policy for pulling OCI objects. Possible values are: + Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. + Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. + IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. + Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. + type: string + reference: + description: |- + Required: Image or artifact reference to be used. + Behaves in the same way as pod.spec.containers[*].image. + Pull secrets will be assembled in the same way as for the container image by looking up node credentials, SA image pull secrets, and pod spec image pull secrets. + More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management to default or override + container images in workload controllers like Deployments and StatefulSets. + type: string + type: object + iscsi: + description: |- + iscsi represents an ISCSI Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://examples.k8s.io/volumes/iscsi/README.md + properties: + chapAuthDiscovery: + description: chapAuthDiscovery defines whether + support iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: chapAuthSession defines whether + support iSCSI Session CHAP authentication + type: boolean + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + type: string + initiatorName: + description: |- + initiatorName is the custom iSCSI Initiator Name. + If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface + : will be created for the connection. + type: string + iqn: + description: iqn is the target iSCSI Qualified + Name. + type: string + iscsiInterface: + default: default + description: |- + iscsiInterface is the interface Name that uses an iSCSI transport. + Defaults to 'default' (tcp). + type: string + lun: + description: lun represents iSCSI Target + Lun number. + format: int32 + type: integer + portals: + description: |- + portals is the iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + items: + type: string + type: array + x-kubernetes-list-type: atomic + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + type: boolean + secretRef: + description: secretRef is the CHAP Secret + for iSCSI target and initiator authentication + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + description: |- + targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + nfs: + description: |- + nfs represents an NFS mount on the host that shares a pod's lifetime + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + properties: + path: + description: |- + path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + readOnly: + description: |- + readOnly here will force the NFS export to be mounted with read-only permissions. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: boolean + server: + description: |- + server is the hostname or IP address of the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: |- + persistentVolumeClaimVolumeSource represents a reference to a + PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + properties: + claimName: + description: |- + claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + type: string + readOnly: + description: |- + readOnly Will force the ReadOnly setting in VolumeMounts. + Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: |- + photonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine. + Deprecated: PhotonPersistentDisk is deprecated and the in-tree photonPersistentDisk type is no longer supported. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + pdID: + description: pdID is the ID that identifies + Photon Controller persistent disk + type: string + required: + - pdID + type: object + portworxVolume: + description: |- + portworxVolume represents a portworx volume attached and mounted on kubelets host machine. + Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type + are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate + is on. + properties: + fsType: + description: |- + fSType represents the filesystem type to mount + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: volumeID uniquely identifies + a Portworx volume + type: string + required: + - volumeID + type: object + projected: + description: projected items for all in one + resources secrets, configmaps, and downward + API + properties: + defaultMode: + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + sources: + description: |- + sources is the list of volume projections. Each entry in this list + handles one source. + items: + description: |- + Projection that may be projected along with other supported volume types. + Exactly one of these fields must be set. + properties: + clusterTrustBundle: + description: |- + ClusterTrustBundle allows a pod to access the `.spec.trustBundle` field + of ClusterTrustBundle objects in an auto-updating file. + + Alpha, gated by the ClusterTrustBundleProjection feature gate. + + ClusterTrustBundle objects can either be selected by name, or by the + combination of signer name and a label selector. + + Kubelet performs aggressive normalization of the PEM contents written + into the pod filesystem. Esoteric PEM features such as inter-block + comments and block headers are stripped. Certificates are deduplicated. + The ordering of certificates within the file is arbitrary, and Kubelet + may change the order over time. + properties: + labelSelector: + description: |- + Select all ClusterTrustBundles that match this label selector. Only has + effect if signerName is set. Mutually-exclusive with name. If unset, + interpreted as "match nothing". If set but empty, interpreted as "match + everything". + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + name: + description: |- + Select a single ClusterTrustBundle by object name. Mutually-exclusive + with signerName and labelSelector. + type: string + optional: + description: |- + If true, don't block pod startup if the referenced ClusterTrustBundle(s) + aren't available. If using name, then the named ClusterTrustBundle is + allowed not to exist. If using signerName, then the combination of + signerName and labelSelector is allowed to match zero + ClusterTrustBundles. + type: boolean + path: + description: Relative path from + the volume root to write the + bundle. + type: string + signerName: + description: |- + Select all ClusterTrustBundles that match this signer name. + Mutually-exclusive with name. The contents of all selected + ClusterTrustBundles will be unified and deduplicated. + type: string + required: + - path + type: object + configMap: + description: configMap information + about the configMap data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key + to a path within a volume. + properties: + key: + description: key is the + key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional specify + whether the ConfigMap or its + keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + description: downwardAPI information + about the downwardAPI data to project + properties: + items: + description: Items is a list of + DownwardAPIVolume file + items: + description: DownwardAPIVolumeFile + represents information to + create the file containing + the pod field + properties: + fieldRef: + description: 'Required: + Selects a field of the + pod: only annotations, + labels, name, namespace + and uid are supported.' + properties: + apiVersion: + description: Version + of the schema the + FieldPath is written + in terms of, defaults + to "v1". + type: string + fieldPath: + description: Path of + the field to select + in the specified API + version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: + Path is the relative + path name of the file + to be created. Must not + be absolute or contain + the ''..'' path. Must + be utf-8 encoded. The + first item of the relative + path must not start with + ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container + name: required for + volumes, optional + for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies + the output format + of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: + resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + secret: + description: secret information about + the secret data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key + to a path within a volume. + properties: + key: + description: key is the + key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional field specify + whether the Secret or its key + must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + description: serviceAccountToken is + information about the serviceAccountToken + data to project + properties: + audience: + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. + type: string + expirationSeconds: + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. + format: int64 + type: integer + path: + description: |- + path is the path relative to the mount point of the file to project the + token into. + type: string + required: + - path + type: object + type: object + type: array + x-kubernetes-list-type: atomic + type: object + quobyte: + description: |- + quobyte represents a Quobyte mount on the host that shares a pod's lifetime. + Deprecated: Quobyte is deprecated and the in-tree quobyte type is no longer supported. + properties: + group: + description: |- + group to map volume access to + Default is no group + type: string + readOnly: + description: |- + readOnly here will force the Quobyte volume to be mounted with read-only permissions. + Defaults to false. + type: boolean + registry: + description: |- + registry represents a single or multiple Quobyte Registry services + specified as a string as host:port pair (multiple entries are separated with commas) + which acts as the central registry for volumes + type: string + tenant: + description: |- + tenant owning the given Quobyte volume in the Backend + Used with dynamically provisioned Quobyte volumes, value is set by the plugin + type: string + user: + description: |- + user to map volume access to + Defaults to serivceaccount user + type: string + volume: + description: volume is a string that references + an already created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: |- + rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. + Deprecated: RBD is deprecated and the in-tree rbd type is no longer supported. + More info: https://examples.k8s.io/volumes/rbd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + type: string + image: + description: |- + image is the rados image name. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + keyring: + default: /etc/ceph/keyring + description: |- + keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + monitors: + description: |- + monitors is a collection of Ceph monitors. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + items: + type: string + type: array + x-kubernetes-list-type: atomic + pool: + default: rbd + description: |- + pool is the rados pool name. + Default is rbd. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: boolean + secretRef: + description: |- + secretRef is name of the authentication secret for RBDUser. If provided + overrides keyring. + Default is nil. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + user: + default: admin + description: |- + user is the rados user name. + Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + required: + - image + - monitors + type: object + scaleIO: + description: |- + scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. + Deprecated: ScaleIO is deprecated and the in-tree scaleIO type is no longer supported. + properties: + fsType: + default: xfs + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". + Default is "xfs". + type: string + gateway: + description: gateway is the host address + of the ScaleIO API Gateway. + type: string + protectionDomain: + description: protectionDomain is the name + of the ScaleIO Protection Domain for the + configured storage. + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef references to the secret for ScaleIO user and other + sensitive information. If this is not provided, Login operation will fail. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + description: sslEnabled Flag enable/disable + SSL communication with Gateway, default + false + type: boolean + storageMode: + default: ThinProvisioned + description: |- + storageMode indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. + Default is ThinProvisioned. + type: string + storagePool: + description: storagePool is the ScaleIO + Storage Pool associated with the protection + domain. + type: string + system: + description: system is the name of the storage + system as configured in ScaleIO. + type: string + volumeName: + description: |- + volumeName is the name of a volume already created in the ScaleIO system + that is associated with this volume source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + properties: + defaultMode: + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + optional: + description: optional field specify whether + the Secret or its keys must be defined + type: boolean + secretName: + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: string + type: object + storageos: + description: |- + storageOS represents a StorageOS volume attached and mounted on Kubernetes nodes. + Deprecated: StorageOS is deprecated and the in-tree storageos type is no longer supported. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef specifies the secret to use for obtaining the StorageOS API + credentials. If not specified, default values will be attempted. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + description: |- + volumeName is the human-readable name of the StorageOS volume. Volume + names are only unique within a namespace. + type: string + volumeNamespace: + description: |- + volumeNamespace specifies the scope of the volume within StorageOS. If no + namespace is specified then the Pod's namespace will be used. This allows the + Kubernetes name scoping to be mirrored within StorageOS for tighter integration. + Set VolumeName to any name to override the default behaviour. + Set to "default" if you are not using namespaces within StorageOS. + Namespaces that do not pre-exist within StorageOS will be created. + type: string + type: object + vsphereVolume: + description: |- + vsphereVolume represents a vSphere volume attached and mounted on kubelets host machine. + Deprecated: VsphereVolume is deprecated. All operations for the in-tree vsphereVolume type + are redirected to the csi.vsphere.vmware.com CSI driver. + properties: + fsType: + description: |- + fsType is filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + storagePolicyID: + description: storagePolicyID is the storage + Policy Based Management (SPBM) profile + ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: storagePolicyName is the storage + Policy Based Management (SPBM) profile + name. + type: string + volumePath: + description: volumePath is the path that + identifies vSphere volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + type: object + strategy: + description: The daemonset strategy to use to replace + existing pods with new ones. + properties: + rollingUpdate: + description: Rolling update config params. Present + only if type = "RollingUpdate". + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of nodes with an existing available DaemonSet pod that + can have an updated DaemonSet pod during during an update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up to a minimum of 1. + Default value is 0. + Example: when this is set to 30%, at most 30% of the total number of nodes + that should be running the daemon pod (i.e. status.desiredNumberScheduled) + can have their a new pod created before the old pod is marked as deleted. + The update starts by launching new pods on 30% of nodes. Once an updated + pod is available (Ready for at least minReadySeconds) the old DaemonSet pod + on that node is marked deleted. If the old pod becomes unavailable for any + reason (Ready transitions to false, is evicted, or is drained) an updated + pod is immediatedly created on that node without considering surge limits. + Allowing surge implies the possibility that the resources consumed by the + daemonset on any given node can double if the readiness check fails, and + so resource intensive daemonsets should take into account that they may + cause evictions during disruption. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of DaemonSet pods that can be unavailable during the + update. Value can be an absolute number (ex: 5) or a percentage of total + number of DaemonSet pods at the start of the update (ex: 10%). Absolute + number is calculated from percentage by rounding up. + This cannot be 0 if MaxSurge is 0 + Default value is 1. + Example: when this is set to 30%, at most 30% of the total number of nodes + that should be running the daemon pod (i.e. status.desiredNumberScheduled) + can have their pods stopped for an update at any given time. The update + starts by stopping at most 30% of those DaemonSet pods and then brings + up new DaemonSet pods in their place. Once the new pods are available, + it then proceeds onto other DaemonSet pods, thus ensuring that at least + 70% of original number of DaemonSet pods are available at all times during + the update. + x-kubernetes-int-or-string: true + type: object + type: + description: Type of daemon set update. Can be "RollingUpdate" + or "OnDelete". Default is RollingUpdate. + type: string + type: object + type: object + envoyDeployment: + description: |- + EnvoyDeployment defines the desired state of the Envoy deployment resource. + If unspecified, default settings for the managed Envoy deployment resource + are applied. + properties: + container: + description: Container defines the desired specification + of main container. + properties: + env: + description: List of environment variables to set + in the container. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. + Must be a C_IDENTIFIER. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: |- + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + properties: + apiVersion: + description: Version of the schema the + FieldPath is written in terms of, + defaults to "v1". + type: string + fieldPath: + description: Path of the field to select + in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + properties: + containerName: + description: 'Container name: required + for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format + of the exposed resources, defaults + to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to + select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret in + the pod's namespace + properties: + key: + description: The key of the secret to + select from. Must be a valid secret + key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + image: + description: Image specifies the EnvoyProxy container + image to be used, instead of the default image. + type: string + resources: + description: |- + Resources required by this container. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry + in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: |- + 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/ + properties: + allowPrivilegeEscalation: + description: |- + AllowPrivilegeEscalation controls whether a process can gain more + privileges than its parent process. This bool directly controls if + the no_new_privs flag will be set on the container process. + AllowPrivilegeEscalation is true always when the container is: + 1) run as Privileged + 2) has CAP_SYS_ADMIN + Note that this field cannot be set when spec.os.name is windows. + type: boolean + appArmorProfile: + description: |- + appArmorProfile is the AppArmor options to use by this container. If set, this profile + overrides the pod's appArmorProfile. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile loaded on the node that should be used. + The profile must be preconfigured on the node to work. + Must match the loaded name of the profile. + Must be set if and only if type is "Localhost". + type: string + type: + description: |- + type indicates which kind of AppArmor profile will be applied. + Valid options are: + Localhost - a profile pre-loaded on the node. + RuntimeDefault - the container runtime's default profile. + Unconfined - no AppArmor enforcement. + type: string + required: + - type + type: object + capabilities: + description: |- + The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the container runtime. + Note that this field cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX + capabilities type + type: string + type: array + x-kubernetes-list-type: atomic + drop: + description: Removed capabilities + items: + description: Capability represent POSIX + capabilities type + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + description: |- + Run container in privileged mode. + Processes in privileged containers are essentially equivalent to root on the host. + Defaults to false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: |- + procMount denotes the type of proc mount to use for the containers. + The default value is Default which uses the container runtime defaults for + readonly paths and masked paths. + This requires the ProcMountType feature flag to be enabled. + Note that this field cannot be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: |- + Whether this container has a read-only root filesystem. + Default is false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label + that applies to the container. + type: string + role: + description: Role is a SELinux role label + that applies to the container. + type: string + type: + description: Type is a SELinux type label + that applies to the container. + type: string + user: + description: User is a SELinux user label + that applies to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by this container. If seccomp options are + provided at both the pod & container level, the container options + override the pod options. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + windowsOptions: + description: |- + The Windows specific settings applied to all containers. + If unspecified, the options from the PodSecurityContext will be used. + If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: |- + GMSACredentialSpec is where the GMSA admission webhook + (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the + GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the + name of the GMSA credential spec to use. + type: string + hostProcess: + description: |- + HostProcess determines if a container should be run as a 'Host Process' container. + All of a Pod's containers must have the same effective HostProcess value + (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). + In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: |- + The UserName in Windows to run the entrypoint of the container process. + Defaults to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + volumeMounts: + description: |- + VolumeMounts are volumes to mount into the container's filesystem. + Cannot be updated. + items: + description: VolumeMount describes a mounting of + a Volume within a container. + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + mountPropagation determines how mounts are propagated from the host + to container and the other way around. + When not set, MountPropagationNone is used. + This field is beta in 1.10. + When RecursiveReadOnly is set to IfPossible or to Enabled, MountPropagation must be None or unspecified + (which defaults to None). + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + recursiveReadOnly: + description: |- + RecursiveReadOnly specifies whether read-only mounts should be handled + recursively. + + If ReadOnly is false, this field has no meaning and must be unspecified. + + If ReadOnly is true, and this field is set to Disabled, the mount is not made + recursively read-only. If this field is set to IfPossible, the mount is made + recursively read-only, if it is supported by the container runtime. If this + field is set to Enabled, the mount is made recursively read-only if it is + supported by the container runtime, otherwise the pod will not be started and + an error will be generated to indicate the reason. + + If this field is set to IfPossible or Enabled, MountPropagation must be set to + None (or be unspecified, which defaults to None). + + If this field is not specified, it is treated as an equivalent of Disabled. + type: string + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: |- + Expanded path within the volume from which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + Defaults to "" (volume's root). + SubPathExpr and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + type: object + initContainers: + description: |- + List of initialization containers belonging to the pod. + More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ + items: + description: A single application container that you + want to run within a pod. + properties: + args: + description: |- + Arguments to the entrypoint. + The container image's CMD is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be updated. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + items: + type: string + type: array + x-kubernetes-list-type: atomic + command: + description: |- + Entrypoint array. Not executed within a shell. + The container image's ENTRYPOINT is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container's environment. If a variable + cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. "$$(VAR_NAME)" will + produce the string literal "$(VAR_NAME)". Escaped references will never be expanded, regardless + of whether the variable exists or not. Cannot be updated. + More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell + items: + type: string + type: array + x-kubernetes-list-type: atomic + env: + description: |- + List of environment variables to set in the container. + Cannot be updated. + items: + description: EnvVar represents an environment + variable present in a Container. + properties: + name: + description: Name of the environment variable. + Must be a C_IDENTIFIER. + type: string + value: + description: |- + Variable references $(VAR_NAME) are expanded + using the previously defined environment variables in the container and + any service environment variables. If a variable cannot be resolved, + the reference in the input string will be unchanged. Double $$ are reduced + to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. + "$$(VAR_NAME)" will produce the string literal "$(VAR_NAME)". + Escaped references will never be expanded, regardless of whether the variable + exists or not. + Defaults to "". + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + fieldRef: + description: |- + Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, + spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs. + properties: + apiVersion: + description: Version of the schema + the FieldPath is written in terms + of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to + select in the specified API version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported. + properties: + containerName: + description: 'Container name: required + for volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output + format of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to + select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + secretKeyRef: + description: Selects a key of a secret + in the pod's namespace + properties: + key: + description: The key of the secret + to select from. Must be a valid + secret key. + type: string + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret + or its key must be defined + type: boolean + required: + - key + type: object + x-kubernetes-map-type: atomic + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + envFrom: + description: |- + List of sources to populate environment variables in the container. + The keys defined within a source must be a C_IDENTIFIER. All invalid keys + will be reported as an event when the container is starting. When a key exists in multiple + sources, the value associated with the last source will take precedence. + Values defined by an Env with a duplicate key will take precedence. + Cannot be updated. + items: + description: EnvFromSource represents the source + of a set of ConfigMaps or Secrets + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the ConfigMap + must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + prefix: + description: Optional text to prepend to the + name of each environment variable. Must + be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: Specify whether the Secret + must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + type: object + type: array + x-kubernetes-list-type: atomic + image: + description: |- + Container image name. + More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management to default or override + container images in workload controllers like Deployments and StatefulSets. + type: string + imagePullPolicy: + description: |- + Image pull policy. + One of Always, Never, IfNotPresent. + Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/containers/images#updating-images + type: string + lifecycle: + description: |- + Actions that the management system should take in response to container lifecycle events. + Cannot be updated. + properties: + postStart: + description: |- + PostStart is called immediately after a container is created. If the handler fails, + the container is terminated and restarted according to its restart policy. + Other management of the container blocks until the hook completes. + More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks + properties: + exec: + description: Exec specifies a command to + execute in the container. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + description: HTTPGet specifies an HTTP GET + request to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in + the request. HTTP allows repeated + headers. + items: + description: HTTPHeader describes + a custom header to be used in HTTP + probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field + value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + sleep: + description: Sleep represents a duration + that the container should sleep. + properties: + seconds: + description: Seconds is the number of + seconds to sleep. + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + description: |- + Deprecated. TCPSocket is NOT supported as a LifecycleHandler and kept + for backward compatibility. There is no validation of this field and + lifecycle hooks will fail at runtime when it is specified. + properties: + host: + description: 'Optional: Host name to + connect to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + description: |- + PreStop is called immediately before a container is terminated due to an + API request or management event such as liveness/startup probe failure, + preemption, resource contention, etc. The handler is not called if the + container crashes or exits. The Pod's termination grace period countdown begins before the + PreStop hook is executed. Regardless of the outcome of the handler, the + container will eventually terminate within the Pod's termination grace + period (unless delayed by finalizers). Other management of the container blocks until the hook completes + or until the termination grace period is reached. + More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks + properties: + exec: + description: Exec specifies a command to + execute in the container. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + httpGet: + description: HTTPGet specifies an HTTP GET + request to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in + the request. HTTP allows repeated + headers. + items: + description: HTTPHeader describes + a custom header to be used in HTTP + probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field + value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + sleep: + description: Sleep represents a duration + that the container should sleep. + properties: + seconds: + description: Seconds is the number of + seconds to sleep. + format: int64 + type: integer + required: + - seconds + type: object + tcpSocket: + description: |- + Deprecated. TCPSocket is NOT supported as a LifecycleHandler and kept + for backward compatibility. There is no validation of this field and + lifecycle hooks will fail at runtime when it is specified. + properties: + host: + description: 'Optional: Host name to + connect to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + stopSignal: + description: |- + StopSignal defines which signal will be sent to a container when it is being stopped. + If not specified, the default is defined by the container runtime in use. + StopSignal can only be set for Pods with a non-empty .spec.os.name + type: string + type: object + livenessProbe: + description: |- + Periodic probe of container liveness. + Container will be restarted if the probe fails. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + properties: + exec: + description: Exec specifies a command to execute + in the container. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies a GRPC HealthCheckRequest. + properties: + port: + description: Port number of the gRPC service. + Number must be in the range 1 to 65535. + format: int32 + type: integer + service: + default: "" + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies an HTTP GET request + to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies a connection + to a TCP port. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object + name: + description: |- + Name of the container specified as a DNS_LABEL. + Each container in a pod must have a unique name (DNS_LABEL). + Cannot be updated. + type: string + ports: + description: |- + List of ports to expose from the container. Not specifying a port here + DOES NOT prevent that port from being exposed. Any port which is + listening on the default "0.0.0.0" address inside a container will be + accessible from the network. + Modifying this array with strategic merge patch may corrupt the data. + For more information See https://github.com/kubernetes/kubernetes/issues/108255. + Cannot be updated. + items: + description: ContainerPort represents a network + port in a single container. + properties: + containerPort: + description: |- + Number of port to expose on the pod's IP address. + This must be a valid port number, 0 < x < 65536. + format: int32 + type: integer + hostIP: + description: What host IP to bind the external + port to. + type: string + hostPort: + description: |- + Number of port to expose on the host. + If specified, this must be a valid port number, 0 < x < 65536. + If HostNetwork is specified, this must match ContainerPort. + Most containers do not need this. + format: int32 + type: integer + name: + description: |- + If specified, this must be an IANA_SVC_NAME and unique within the pod. Each + named port in a pod must have a unique name. Name for the port that can be + referred to by services. + type: string + protocol: + default: TCP + description: |- + Protocol for port. Must be UDP, TCP, or SCTP. + Defaults to "TCP". + type: string + required: + - containerPort + type: object + type: array + x-kubernetes-list-map-keys: + - containerPort + - protocol + x-kubernetes-list-type: map + readinessProbe: + description: |- + Periodic probe of container service readiness. + Container will be removed from service endpoints if the probe fails. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + properties: + exec: + description: Exec specifies a command to execute + in the container. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies a GRPC HealthCheckRequest. + properties: + port: + description: Port number of the gRPC service. + Number must be in the range 1 to 65535. + format: int32 + type: integer + service: + default: "" + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies an HTTP GET request + to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies a connection + to a TCP port. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object + resizePolicy: + description: Resources resize policy for the container. + items: + description: ContainerResizePolicy represents + resource resize policy for the container. + properties: + resourceName: + description: |- + Name of the resource to which this resource resize policy applies. + Supported values: cpu, memory. + type: string + restartPolicy: + description: |- + Restart policy to apply when specified resource is resized. + If not specified, it defaults to NotRequired. + type: string + required: + - resourceName + - restartPolicy + type: object + type: array + x-kubernetes-list-type: atomic + resources: + description: |- + Compute Resources required by this container. + Cannot be updated. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one + entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + restartPolicy: + description: |- + RestartPolicy defines the restart behavior of individual containers in a pod. + This field may only be set for init containers, and the only allowed value is "Always". + For non-init containers or when this field is not specified, + the restart behavior is defined by the Pod's restart policy and the container type. + Setting the RestartPolicy as "Always" for the init container will have the following effect: + this init container will be continually restarted on + exit until all regular containers have terminated. Once all regular + containers have completed, all init containers with restartPolicy "Always" + will be shut down. This lifecycle differs from normal init containers and + is often referred to as a "sidecar" container. Although this init + container still starts in the init container sequence, it does not wait + for the container to complete before proceeding to the next init + container. Instead, the next init container starts immediately after this + init container is started, or after any startupProbe has successfully + completed. + type: string + securityContext: + description: |- + 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/ + properties: + allowPrivilegeEscalation: + description: |- + AllowPrivilegeEscalation controls whether a process can gain more + privileges than its parent process. This bool directly controls if + the no_new_privs flag will be set on the container process. + AllowPrivilegeEscalation is true always when the container is: + 1) run as Privileged + 2) has CAP_SYS_ADMIN + Note that this field cannot be set when spec.os.name is windows. + type: boolean + appArmorProfile: + description: |- + appArmorProfile is the AppArmor options to use by this container. If set, this profile + overrides the pod's appArmorProfile. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile loaded on the node that should be used. + The profile must be preconfigured on the node to work. + Must match the loaded name of the profile. + Must be set if and only if type is "Localhost". + type: string + type: + description: |- + type indicates which kind of AppArmor profile will be applied. + Valid options are: + Localhost - a profile pre-loaded on the node. + RuntimeDefault - the container runtime's default profile. + Unconfined - no AppArmor enforcement. + type: string + required: + - type + type: object + capabilities: + description: |- + The capabilities to add/drop when running containers. + Defaults to the default set of capabilities granted by the container runtime. + Note that this field cannot be set when spec.os.name is windows. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX + capabilities type + type: string + type: array + x-kubernetes-list-type: atomic + drop: + description: Removed capabilities + items: + description: Capability represent POSIX + capabilities type + type: string + type: array + x-kubernetes-list-type: atomic + type: object + privileged: + description: |- + Run container in privileged mode. + Processes in privileged containers are essentially equivalent to root on the host. + Defaults to false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + procMount: + description: |- + procMount denotes the type of proc mount to use for the containers. + The default value is Default which uses the container runtime defaults for + readonly paths and masked paths. + This requires the ProcMountType feature flag to be enabled. + Note that this field cannot be set when spec.os.name is windows. + type: string + readOnlyRootFilesystem: + description: |- + Whether this container has a read-only root filesystem. + Default is false. + Note that this field cannot be set when spec.os.name is windows. + type: boolean + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to the container. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label + that applies to the container. + type: string + role: + description: Role is a SELinux role label + that applies to the container. + type: string + type: + description: Type is a SELinux type label + that applies to the container. + type: string + user: + description: User is a SELinux user label + that applies to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by this container. If seccomp options are + provided at both the pod & container level, the container options + override the pod options. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + windowsOptions: + description: |- + The Windows specific settings applied to all containers. + If unspecified, the options from the PodSecurityContext will be used. + If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: |- + GMSACredentialSpec is where the GMSA admission webhook + (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the + GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the + name of the GMSA credential spec to use. + type: string + hostProcess: + description: |- + HostProcess determines if a container should be run as a 'Host Process' container. + All of a Pod's containers must have the same effective HostProcess value + (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). + In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: |- + The UserName in Windows to run the entrypoint of the container process. + Defaults to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + startupProbe: + description: |- + StartupProbe indicates that the Pod has successfully initialized. + If specified, no other probes are executed until this completes successfully. + If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. + This can be used to provide different probe parameters at the beginning of a Pod's lifecycle, + when it might take a long time to load data or warm a cache, than during steady-state operation. + This cannot be updated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + properties: + exec: + description: Exec specifies a command to execute + in the container. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies a GRPC HealthCheckRequest. + properties: + port: + description: Port number of the gRPC service. + Number must be in the range 1 to 65535. + format: int32 + type: integer + service: + default: "" + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies an HTTP GET request + to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP + server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies a connection + to a TCP port. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object + stdin: + description: |- + Whether this container should allocate a buffer for stdin in the container runtime. If this + is not set, reads from stdin in the container will always result in EOF. + Default is false. + type: boolean + stdinOnce: + description: |- + Whether the container runtime should close the stdin channel after it has been opened by + a single attach. When stdin is true the stdin stream will remain open across multiple attach + sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the + first client attaches to stdin, and then remains open and accepts data until the client disconnects, + at which time stdin is closed and remains closed until the container is restarted. If this + flag is false, a container processes that reads from stdin will never receive an EOF. + Default is false + type: boolean + terminationMessagePath: + description: |- + Optional: Path at which the file to which the container's termination message + will be written is mounted into the container's filesystem. + Message written is intended to be brief final status, such as an assertion failure message. + Will be truncated by the node if greater than 4096 bytes. The total message length across + all containers will be limited to 12kb. + Defaults to /dev/termination-log. + Cannot be updated. + type: string + terminationMessagePolicy: + description: |- + Indicate how the termination message should be populated. File will use the contents of + terminationMessagePath to populate the container status message on both success and failure. + FallbackToLogsOnError will use the last chunk of container log output if the termination + message file is empty and the container exited with an error. + The log output is limited to 2048 bytes or 80 lines, whichever is smaller. + Defaults to File. + Cannot be updated. + type: string + tty: + description: |- + Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. + Default is false. + type: boolean + volumeDevices: + description: volumeDevices is the list of block + devices to be used by the container. + items: + description: volumeDevice describes a mapping + of a raw block device within a container. + properties: + devicePath: + description: devicePath is the path inside + of the container that the device will be + mapped to. + type: string + name: + description: name must match the name of a + persistentVolumeClaim in the pod + type: string + required: + - devicePath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - devicePath + x-kubernetes-list-type: map + volumeMounts: + description: |- + Pod volumes to mount into the container's filesystem. + Cannot be updated. + items: + description: VolumeMount describes a mounting + of a Volume within a container. + properties: + mountPath: + description: |- + Path within the container at which the volume should be mounted. Must + not contain ':'. + type: string + mountPropagation: + description: |- + mountPropagation determines how mounts are propagated from the host + to container and the other way around. + When not set, MountPropagationNone is used. + This field is beta in 1.10. + When RecursiveReadOnly is set to IfPossible or to Enabled, MountPropagation must be None or unspecified + (which defaults to None). + type: string + name: + description: This must match the Name of a + Volume. + type: string + readOnly: + description: |- + Mounted read-only if true, read-write otherwise (false or unspecified). + Defaults to false. + type: boolean + recursiveReadOnly: + description: |- + RecursiveReadOnly specifies whether read-only mounts should be handled + recursively. + + If ReadOnly is false, this field has no meaning and must be unspecified. + + If ReadOnly is true, and this field is set to Disabled, the mount is not made + recursively read-only. If this field is set to IfPossible, the mount is made + recursively read-only, if it is supported by the container runtime. If this + field is set to Enabled, the mount is made recursively read-only if it is + supported by the container runtime, otherwise the pod will not be started and + an error will be generated to indicate the reason. + + If this field is set to IfPossible or Enabled, MountPropagation must be set to + None (or be unspecified, which defaults to None). + + If this field is not specified, it is treated as an equivalent of Disabled. + type: string + subPath: + description: |- + Path within the volume from which the container's volume should be mounted. + Defaults to "" (volume's root). + type: string + subPathExpr: + description: |- + Expanded path within the volume from which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. + Defaults to "" (volume's root). + SubPathExpr and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + x-kubernetes-list-map-keys: + - mountPath + x-kubernetes-list-type: map + workingDir: + description: |- + Container's working directory. + If not specified, the container runtime's default will be used, which + might be configured in the container image. + Cannot be updated. + type: string + required: + - name + type: object + type: array + name: + description: |- + Name of the deployment. + When unset, this defaults to an autogenerated name. + type: string + patch: + description: Patch defines how to perform the patch operation + to deployment + properties: + type: + description: |- + Type is the type of merge operation to perform + + By default, StrategicMerge is used as the patch type. + type: string + value: + description: Object contains the raw configuration + for merged object + x-kubernetes-preserve-unknown-fields: true + required: + - value + type: object + pod: + description: Pod defines the desired specification of + pod. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + 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. + If it's null, this PodAffinityTerm matches with no 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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. + If it's null, this PodAffinityTerm matches with no 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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. + If it's null, this PodAffinityTerm matches with no 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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. + If it's null, this PodAffinityTerm matches with no 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + 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 + x-kubernetes-list-type: atomic + type: object + type: object + annotations: + additionalProperties: + type: string + description: |- + Annotations are the annotations that should be appended to the pods. + By default, no pod annotations are appended. + type: object + imagePullSecrets: + description: |- + ImagePullSecrets is an optional list of references to secrets + in the same namespace to use for pulling any of the images used by this PodSpec. + If specified, these secrets will be passed to individual puller implementations for them to use. + More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + labels: + additionalProperties: + type: string + description: |- + Labels are the additional labels that should be tagged to the pods. + By default, no additional pod labels are tagged. + type: object + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector is a selector which must be true for the pod to fit on a node. + Selector which must match a node's labels for the pod to be scheduled on that node. + More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + type: object + securityContext: + description: |- + SecurityContext holds pod-level security attributes and common container settings. + Optional: Defaults to empty. See type description for default values of each field. + properties: + appArmorProfile: + description: |- + appArmorProfile is the AppArmor options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile loaded on the node that should be used. + The profile must be preconfigured on the node to work. + Must match the loaded name of the profile. + Must be set if and only if type is "Localhost". + type: string + type: + description: |- + type indicates which kind of AppArmor profile will be applied. + Valid options are: + Localhost - a profile pre-loaded on the node. + RuntimeDefault - the container runtime's default profile. + Unconfined - no AppArmor enforcement. + type: string + required: + - type + type: object + fsGroup: + description: |- + A special supplemental group that applies to all containers in a pod. + Some volume types allow the Kubelet to change the ownership of that volume + to be owned by the pod: + + 1. The owning GID will be the FSGroup + 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + 3. The permission bits are OR'd with rw-rw---- + + If unset, the Kubelet will not modify the ownership and permissions of any volume. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + fsGroupChangePolicy: + description: |- + fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + before being exposed inside Pod. This field will only apply to + volume types which support fsGroup based ownership(and permissions). + It will have no effect on ephemeral volume types such as: secret, configmaps + and emptydir. + Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + Note that this field cannot be set when spec.os.name is windows. + type: string + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxChangePolicy: + description: |- + seLinuxChangePolicy defines how the container's SELinux label is applied to all volumes used by the Pod. + It has no effect on nodes that do not support SELinux or to volumes does not support SELinux. + Valid values are "MountOption" and "Recursive". + + "Recursive" means relabeling of all files on all Pod volumes by the container runtime. + This may be slow for large volumes, but allows mixing privileged and unprivileged Pods sharing the same volume on the same node. + + "MountOption" mounts all eligible Pod volumes with `-o context` mount option. + This requires all Pods that share the same volume to use the same SELinux label. + It is not possible to share the same volume among privileged and unprivileged Pods. + Eligible volumes are in-tree FibreChannel and iSCSI volumes, and all CSI volumes + whose CSI driver announces SELinux support by setting spec.seLinuxMount: true in their + CSIDriver instance. Other volumes are always re-labelled recursively. + "MountOption" value is allowed only when SELinuxMount feature gate is enabled. + + If not specified and SELinuxMount feature gate is enabled, "MountOption" is used. + If not specified and SELinuxMount feature gate is disabled, "MountOption" is used for ReadWriteOncePod volumes + and "Recursive" for all other volumes. + + This field affects only Pods that have SELinux label set, either in PodSecurityContext or in SecurityContext of all containers. + + All Pods that use the same volume should use the same seLinuxChangePolicy, otherwise some pods can get stuck in ContainerCreating state. + Note that this field cannot be set when spec.os.name is windows. + type: string + seLinuxOptions: + description: |- + The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in SecurityContext. If set in + both SecurityContext and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label + that applies to the container. + type: string + role: + description: Role is a SELinux role label + that applies to the container. + type: string + type: + description: Type is a SELinux type label + that applies to the container. + type: string + user: + description: User is a SELinux user label + that applies to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + supplementalGroups: + description: |- + A list of groups applied to the first process run in each container, in + addition to the container's primary GID and fsGroup (if specified). If + the SupplementalGroupsPolicy feature is enabled, the + supplementalGroupsPolicy field determines whether these are in addition + to or instead of any group memberships defined in the container image. + If unspecified, no additional groups are added, though group memberships + defined in the container image may still be used, depending on the + supplementalGroupsPolicy field. + Note that this field cannot be set when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + supplementalGroupsPolicy: + description: |- + Defines how supplemental groups of the first container processes are calculated. + Valid values are "Merge" and "Strict". If not specified, "Merge" is used. + (Alpha) Using the field requires the SupplementalGroupsPolicy feature gate to be enabled + and the container runtime must implement support for this feature. + Note that this field cannot be set when spec.os.name is windows. + type: string + sysctls: + description: |- + Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + sysctls (by the container runtime) might fail to launch. + Note that this field cannot be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter + to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + windowsOptions: + description: |- + The Windows specific settings applied to all containers. + If unspecified, the options within a container's SecurityContext will be used. + If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. + Note that this field cannot be set when spec.os.name is linux. + properties: + gmsaCredentialSpec: + description: |- + GMSACredentialSpec is where the GMSA admission webhook + (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the + GMSA credential spec named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the + name of the GMSA credential spec to use. + type: string + hostProcess: + description: |- + HostProcess determines if a container should be run as a 'Host Process' container. + All of a Pod's containers must have the same effective HostProcess value + (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). + In addition, if HostProcess is true then HostNetwork must also be set to true. + type: boolean + runAsUserName: + description: |- + The UserName in Windows to run the entrypoint of the container process. + Defaults to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + 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 + topologySpreadConstraints: + description: |- + TopologySpreadConstraints describes how a group of pods ought to spread across topology + domains. Scheduler will schedule pods in a way which abides by the constraints. + All topologySpreadConstraints are ANDed. + items: + description: TopologySpreadConstraint specifies + how to spread matching pods among the given topology. + properties: + labelSelector: + description: |- + LabelSelector is used to find matching pods. + Pods that match this label selector are counted to determine the number of pods + in their corresponding topology domain. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select the pods over which + spreading will be calculated. The keys are used to lookup values from the + incoming pod labels, those key-value labels are ANDed with labelSelector + to select the group of existing pods over which spreading will be calculated + for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. + MatchLabelKeys cannot be set when LabelSelector isn't set. + Keys that don't exist in the incoming pod labels will + be ignored. A null or empty list means only match against labelSelector. + + This is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default). + items: + type: string + type: array + x-kubernetes-list-type: atomic + maxSkew: + description: |- + MaxSkew describes the degree to which pods may be unevenly distributed. + When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference + between the number of matching pods in the target topology and the global minimum. + The global minimum is the minimum number of matching pods in an eligible domain + or zero if the number of eligible domains is less than MinDomains. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 2/2/1: + In this case, the global minimum is 1. + | zone1 | zone2 | zone3 | + | P P | P P | P | + - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; + scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) + violate MaxSkew(1). + - if MaxSkew is 2, incoming pod can be scheduled onto any zone. + When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence + to topologies that satisfy it. + It's a required field. Default value is 1 and 0 is not allowed. + format: int32 + type: integer + minDomains: + description: |- + MinDomains indicates a minimum number of eligible domains. + When the number of eligible domains with matching topology keys is less than minDomains, + Pod Topology Spread treats "global minimum" as 0, and then the calculation of Skew is performed. + And when the number of eligible domains with matching topology keys equals or greater than minDomains, + this value has no effect on scheduling. + As a result, when the number of eligible domains is less than minDomains, + scheduler won't schedule more than maxSkew Pods to those domains. + If value is nil, the constraint behaves as if MinDomains is equal to 1. + Valid values are integers greater than 0. + When value is not nil, WhenUnsatisfiable must be DoNotSchedule. + + For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same + labelSelector spread as 2/2/2: + | zone1 | zone2 | zone3 | + | P P | P P | P P | + The number of domains is less than 5(MinDomains), so "global minimum" is treated as 0. + In this situation, new pod with the same labelSelector cannot be scheduled, + because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, + it will violate MaxSkew. + format: int32 + type: integer + nodeAffinityPolicy: + description: |- + NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector + when calculating pod topology spread skew. Options are: + - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. + - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations. + + If this value is nil, the behavior is equivalent to the Honor policy. + type: string + nodeTaintsPolicy: + description: |- + NodeTaintsPolicy indicates how we will treat node taints when calculating + pod topology spread skew. Options are: + - Honor: nodes without taints, along with tainted nodes for which the incoming pod + has a toleration, are included. + - Ignore: node taints are ignored. All nodes are included. + + If this value is nil, the behavior is equivalent to the Ignore policy. + type: string + topologyKey: + description: |- + TopologyKey is the key of node labels. Nodes that have a label with this key + and identical values are considered to be in the same topology. + We consider each as a "bucket", and try to put balanced number + of pods into each bucket. + We define a domain as a particular instance of a topology. + Also, we define an eligible domain as a domain whose nodes meet the requirements of + nodeAffinityPolicy and nodeTaintsPolicy. + e.g. If TopologyKey is "kubernetes.io/hostname", each Node is a domain of that topology. + And, if TopologyKey is "topology.kubernetes.io/zone", each zone is a domain of that topology. + It's a required field. + type: string + whenUnsatisfiable: + description: |- + WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy + the spread constraint. + - DoNotSchedule (default) tells the scheduler not to schedule it. + - ScheduleAnyway tells the scheduler to schedule the pod in any location, + but giving higher precedence to topologies that would help reduce the + skew. + A constraint is considered "Unsatisfiable" for an incoming pod + if and only if every possible node assignment for that pod would violate + "MaxSkew" on some topology. + For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same + labelSelector spread as 3/1/1: + | zone1 | zone2 | zone3 | + | P P P | P | P | + If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled + to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies + MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler + won't make it *more* imbalanced. + It's a required field. + type: string + required: + - maxSkew + - topologyKey + - whenUnsatisfiable + type: object + type: array + volumes: + description: |- + Volumes that can be mounted by containers belonging to the pod. + More info: https://kubernetes.io/docs/concepts/storage/volumes + items: + description: Volume represents a named volume in + a pod that may be accessed by any container in + the pod. + properties: + awsElasticBlockStore: + description: |- + awsElasticBlockStore represents an AWS Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + Deprecated: AWSElasticBlockStore is deprecated. All operations for the in-tree + awsElasticBlockStore type are redirected to the ebs.csi.aws.com CSI driver. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + format: int32 + type: integer + readOnly: + description: |- + readOnly value true will force the readOnly setting in VolumeMounts. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: boolean + volumeID: + description: |- + volumeID is unique ID of the persistent disk resource in AWS (Amazon EBS volume). + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + type: string + required: + - volumeID + type: object + azureDisk: + description: |- + azureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. + Deprecated: AzureDisk is deprecated. All operations for the in-tree azureDisk type + are redirected to the disk.csi.azure.com CSI driver. + properties: + cachingMode: + description: 'cachingMode is the Host Caching + mode: None, Read Only, Read Write.' + type: string + diskName: + description: diskName is the Name of the + data disk in the blob storage + type: string + diskURI: + description: diskURI is the URI of data + disk in the blob storage + type: string + fsType: + default: ext4 + description: |- + fsType is Filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + kind: + description: 'kind expected values are Shared: + multiple blob disks per storage account Dedicated: + single blob disk per storage account Managed: + azure managed data disk (only in managed + availability set). defaults to shared' + type: string + readOnly: + default: false + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: |- + azureFile represents an Azure File Service mount on the host and bind mount to the pod. + Deprecated: AzureFile is deprecated. All operations for the in-tree azureFile type + are redirected to the file.csi.azure.com CSI driver. + properties: + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: secretName is the name of + secret that contains Azure Storage Account + Name and Key + type: string + shareName: + description: shareName is the azure share + Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: |- + cephFS represents a Ceph FS mount on the host that shares a pod's lifetime. + Deprecated: CephFS is deprecated and the in-tree cephfs type is no longer supported. + properties: + monitors: + description: |- + monitors is Required: Monitors is a collection of Ceph monitors + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + items: + type: string + type: array + x-kubernetes-list-type: atomic + path: + description: 'path is Optional: Used as + the mounted root, rather than the full + Ceph tree, default is /' + type: string + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: boolean + secretFile: + description: |- + secretFile is Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + secretRef: + description: |- + secretRef is Optional: SecretRef is reference to the authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + user: + description: |- + user is optional: User is the rados user name, default is admin + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it + type: string + required: + - monitors + type: object + cinder: + description: |- + cinder represents a cinder volume attached and mounted on kubelets host machine. + Deprecated: Cinder is deprecated. All operations for the in-tree cinder type + are redirected to the cinder.csi.openstack.org CSI driver. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: boolean + secretRef: + description: |- + secretRef is optional: points to a secret object containing parameters used to connect + to OpenStack. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + volumeID: + description: |- + volumeID used to identify the volume in cinder. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md + type: string + required: + - volumeID + type: object + configMap: + description: configMap represents a configMap + that should populate this volume + properties: + defaultMode: + description: |- + defaultMode is optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional specify whether the + ConfigMap or its keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + csi: + description: csi (Container Storage Interface) + represents ephemeral storage that is handled + by certain external CSI drivers. + properties: + driver: + description: |- + driver is the name of the CSI driver that handles this volume. + Consult with your admin for the correct name as registered in the cluster. + type: string + fsType: + description: |- + fsType to mount. Ex. "ext4", "xfs", "ntfs". + If not provided, the empty value is passed to the associated CSI driver + which will determine the default filesystem to apply. + type: string + nodePublishSecretRef: + description: |- + nodePublishSecretRef is a reference to the secret object containing + sensitive information to pass to the CSI driver to complete the CSI + NodePublishVolume and NodeUnpublishVolume calls. + This field is optional, and may be empty if no secret is required. If the + secret object contains more than one secret, all secret references are passed. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + readOnly: + description: |- + readOnly specifies a read-only configuration for the volume. + Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: |- + volumeAttributes stores driver-specific properties that are passed to the CSI + driver. Consult your driver's documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: downwardAPI represents downward + API about the pod that should populate this + volume + properties: + defaultMode: + description: |- + Optional: mode bits to use on created files by default. Must be a + Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: Items is a list of downward + API volume file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects a + field of the pod: only annotations, + labels, name, namespace and uid + are supported.' + properties: + apiVersion: + description: Version of the schema + the FieldPath is written in + terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field + to select in the specified API + version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file to + be created. Must not be absolute + or contain the ''..'' path. Must + be utf-8 encoded. The first item + of the relative path must not start + with ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container name: + required for volumes, optional + for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output + format of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + emptyDir: + description: |- + emptyDir represents a temporary directory that shares a pod's lifetime. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + properties: + medium: + description: |- + medium represents what type of storage medium should back this directory. + The default is "" which means to use the node's default medium. + Must be an empty string (default) or Memory. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: |- + sizeLimit is the total amount of local storage required for this EmptyDir volume. + The size limit is also applicable for memory medium. + The maximum usage on memory medium EmptyDir would be the minimum value between + the SizeLimit specified here and the sum of memory limits of all containers in a pod. + The default is nil which means that the limit is undefined. + More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + description: |- + ephemeral represents a volume that is handled by a cluster storage driver. + The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, + and deleted when the pod is removed. + + Use this if: + a) the volume is only needed while the pod runs, + b) features of normal volumes like restoring from snapshot or capacity + tracking are needed, + c) the storage driver is specified through a storage class, and + d) the storage driver supports dynamic volume provisioning through + a PersistentVolumeClaim (see EphemeralVolumeSource for more + information on the connection between this volume type + and PersistentVolumeClaim). + + Use PersistentVolumeClaim or one of the vendor-specific + APIs for volumes that persist for longer than the lifecycle + of an individual pod. + + Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to + be used that way - see the documentation of the driver for + more information. + + A pod can use both types of ephemeral volumes and + persistent volumes at the same time. + properties: + volumeClaimTemplate: + description: |- + Will be used to create a stand-alone PVC to provision the volume. + The pod in which this EphemeralVolumeSource is embedded will be the + owner of the PVC, i.e. the PVC will be deleted together with the + pod. The name of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` array + entry. Pod validation will reject the pod if the concatenated name + is not valid for a PVC (for example, too long). + + An existing PVC with that name that is not owned by the pod + will *not* be used for the pod to avoid using an unrelated + volume by mistake. Starting the pod is then blocked until + the unrelated PVC is removed. If such a pre-created PVC is + meant to be used by the pod, the PVC has to updated with an + owner reference to the pod once the pod exists. Normally + this should not be necessary, but it may be useful when + manually reconstructing a broken cluster. + + This field is read-only and no changes will be made by Kubernetes + to the PVC after it has been created. + + Required, must not be nil. + properties: + metadata: + description: |- + May contain labels and annotations that will be copied into the PVC + when creating it. No other fields are allowed and will be rejected during + validation. + type: object + spec: + description: |- + The specification for the PersistentVolumeClaim. The entire content is + copied unchanged into the PVC that gets created from this + template. The same fields as in a PersistentVolumeClaim + are also valid here. + properties: + accessModes: + description: |- + accessModes contains the desired access modes the volume should have. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 + items: + type: string + type: array + x-kubernetes-list-type: atomic + dataSource: + description: |- + dataSource field can be used to specify either: + * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) + * An existing PVC (PersistentVolumeClaim) + If the provisioner or an external controller can support the specified data source, + it will create a new volume based on the contents of the specified data source. + When the AnyVolumeDataSource feature gate is enabled, dataSource contents will be copied to dataSourceRef, + and dataSourceRef contents will be copied to dataSource when dataSourceRef.namespace is not specified. + If the namespace is specified, then dataSourceRef will not be copied to dataSource. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type + of resource being referenced + type: string + name: + description: Name is the name + of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + dataSourceRef: + description: |- + dataSourceRef specifies the object from which to populate the volume with data, if a non-empty + volume is desired. This may be any object from a non-empty API group (non + core object) or a PersistentVolumeClaim object. + When this field is specified, volume binding will only succeed if the type of + the specified object matches some installed volume populator or dynamic + provisioner. + This field will replace the functionality of the dataSource field and as such + if both fields are non-empty, they must have the same value. For backwards + compatibility, when namespace isn't specified in dataSourceRef, + both fields (dataSource and dataSourceRef) will be set to the same + value automatically if one of them is empty and the other is non-empty. + When namespace is specified in dataSourceRef, + dataSource isn't set to the same value and must be empty. + There are three important differences between dataSource and dataSourceRef: + * While dataSource only allows two specific types of objects, dataSourceRef + allows any non-core object, as well as PersistentVolumeClaim objects. + * While dataSource ignores disallowed values (dropping them), dataSourceRef + preserves all values, and generates an error if a disallowed value is + specified. + * While dataSource only allows local objects, dataSourceRef allows objects + in any namespaces. + (Beta) Using this field requires the AnyVolumeDataSource feature gate to be enabled. + (Alpha) Using the namespace field of dataSourceRef requires the CrossNamespaceVolumeDataSource feature gate to be enabled. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type + of resource being referenced + type: string + name: + description: Name is the name + of resource being referenced + type: string + namespace: + description: |- + Namespace is the namespace of resource being referenced + Note that when a namespace is specified, a gateway.networking.k8s.io/ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. + (Alpha) This field requires the CrossNamespaceVolumeDataSource feature gate to be enabled. + type: string + required: + - kind + - name + type: object + resources: + description: |- + resources represents the minimum resources the volume should have. + If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements + that are lower than previous value but must still be higher than capacity recorded in the + status field of the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + selector: + description: selector is a label + query over volumes to consider + for binding. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + storageClassName: + description: |- + storageClassName is the name of the StorageClass required by the claim. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 + type: string + volumeAttributesClassName: + description: |- + volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim. + If specified, the CSI driver will create or update the volume with the attributes defined + in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName, + it can be changed after the claim is created. An empty string value means that no VolumeAttributesClass + will be applied to the claim but it's not allowed to reset this field to empty string once it is set. + If unspecified and the PersistentVolumeClaim is unbound, the default VolumeAttributesClass + will be set by the persistentvolume controller if it exists. + If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be + set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource + exists. + More info: https://kubernetes.io/docs/concepts/storage/volume-attributes-classes/ + (Beta) Using this field requires the VolumeAttributesClass feature gate to be enabled (off by default). + type: string + volumeMode: + description: |- + volumeMode defines what type of volume is required by the claim. + Value of Filesystem is implied when not included in claim spec. + type: string + volumeName: + description: volumeName is the binding + reference to the PersistentVolume + backing this claim. + type: string + type: object + required: + - spec + type: object + type: object + fc: + description: fc represents a Fibre Channel resource + that is attached to a kubelet's host machine + and then exposed to the pod. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + lun: + description: 'lun is Optional: FC target + lun number' + format: int32 + type: integer + readOnly: + description: |- + readOnly is Optional: Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + targetWWNs: + description: 'targetWWNs is Optional: FC + target worldwide names (WWNs)' + items: + type: string + type: array + x-kubernetes-list-type: atomic + wwids: + description: |- + wwids Optional: FC volume world wide identifiers (wwids) + Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + flexVolume: + description: |- + flexVolume represents a generic volume resource that is + provisioned/attached using an exec based plugin. + Deprecated: FlexVolume is deprecated. Consider using a CSIDriver instead. + properties: + driver: + description: driver is the name of the driver + to use for this volume. + type: string + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". The default filesystem depends on FlexVolume script. + type: string + options: + additionalProperties: + type: string + description: 'options is Optional: this + field holds extra command options if any.' + type: object + readOnly: + description: |- + readOnly is Optional: defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef is Optional: secretRef is reference to the secret object containing + sensitive information to pass to the plugin scripts. This may be + empty if no secret object is specified. If the secret object + contains more than one secret, all secrets are passed to the plugin + scripts. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + required: + - driver + type: object + flocker: + description: |- + flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running. + Deprecated: Flocker is deprecated and the in-tree flocker type is no longer supported. + properties: + datasetName: + description: |- + datasetName is Name of the dataset stored as metadata -> name on the dataset for Flocker + should be considered as deprecated + type: string + datasetUUID: + description: datasetUUID is the UUID of + the dataset. This is unique identifier + of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: |- + gcePersistentDisk represents a GCE Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + Deprecated: GCEPersistentDisk is deprecated. All operations for the in-tree + gcePersistentDisk type are redirected to the pd.csi.storage.gke.io CSI driver. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + properties: + fsType: + description: |- + fsType is filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + partition: + description: |- + partition is the partition in the volume that you want to mount. + If omitted, the default is to mount by volume name. + Examples: For volume /dev/sda1, you specify the partition as "1". + Similarly, the volume partition for /dev/sda is "0" (or you can leave the property empty). + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + format: int32 + type: integer + pdName: + description: |- + pdName is unique name of the PD resource in GCE. Used to identify the disk in GCE. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + type: boolean + required: + - pdName + type: object + gitRepo: + description: |- + gitRepo represents a git repository at a particular revision. + Deprecated: GitRepo is deprecated. To provision a container with a git repo, mount an + EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir + into the Pod's container. + properties: + directory: + description: |- + directory is the target directory name. + Must not contain or start with '..'. If '.' is supplied, the volume directory will be the + git repository. Otherwise, if specified, the volume will contain the git repository in + the subdirectory with the given name. + type: string + repository: + description: repository is the URL + type: string + revision: + description: revision is the commit hash + for the specified revision. + type: string + required: + - repository + type: object + glusterfs: + description: |- + glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. + Deprecated: Glusterfs is deprecated and the in-tree glusterfs type is no longer supported. + More info: https://examples.k8s.io/volumes/glusterfs/README.md + properties: + endpoints: + description: |- + endpoints is the endpoint name that details Glusterfs topology. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + path: + description: |- + path is the Glusterfs volume path. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: string + readOnly: + description: |- + readOnly here will force the Glusterfs volume to be mounted with read-only permissions. + Defaults to false. + More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: |- + hostPath represents a pre-existing file or directory on the host + machine that is directly exposed to the container. This is generally + used for system agents or other privileged things that are allowed + to see the host machine. Most containers will NOT need this. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + properties: + path: + description: |- + path of the directory on the host. + If the path is a symlink, it will follow the link to the real path. + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + type: + description: |- + type for HostPath Volume + Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + type: string + required: + - path + type: object + image: + description: |- + image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. + The volume is resolved at pod startup depending on which PullPolicy value is provided: + + - Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. + - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. + - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. + + The volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. + A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. + The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. + The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. + The volume will be mounted read-only (ro) and non-executable files (noexec). + Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. + The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type. + properties: + pullPolicy: + description: |- + Policy for pulling OCI objects. Possible values are: + Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. + Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. + IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. + Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. + type: string + reference: + description: |- + Required: Image or artifact reference to be used. + Behaves in the same way as pod.spec.containers[*].image. + Pull secrets will be assembled in the same way as for the container image by looking up node credentials, SA image pull secrets, and pod spec image pull secrets. + More info: https://kubernetes.io/docs/concepts/containers/images + This field is optional to allow higher level config management to default or override + container images in workload controllers like Deployments and StatefulSets. + type: string + type: object + iscsi: + description: |- + iscsi represents an ISCSI Disk resource that is attached to a + kubelet's host machine and then exposed to the pod. + More info: https://examples.k8s.io/volumes/iscsi/README.md + properties: + chapAuthDiscovery: + description: chapAuthDiscovery defines whether + support iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: chapAuthSession defines whether + support iSCSI Session CHAP authentication + type: boolean + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + type: string + initiatorName: + description: |- + initiatorName is the custom iSCSI Initiator Name. + If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface + : will be created for the connection. + type: string + iqn: + description: iqn is the target iSCSI Qualified + Name. + type: string + iscsiInterface: + default: default + description: |- + iscsiInterface is the interface Name that uses an iSCSI transport. + Defaults to 'default' (tcp). + type: string + lun: + description: lun represents iSCSI Target + Lun number. + format: int32 + type: integer + portals: + description: |- + portals is the iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + items: + type: string + type: array + x-kubernetes-list-type: atomic + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + type: boolean + secretRef: + description: secretRef is the CHAP Secret + for iSCSI target and initiator authentication + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + targetPortal: + description: |- + targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port + is other than default (typically TCP ports 860 and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: |- + name of the volume. + Must be a DNS_LABEL and unique within the pod. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + nfs: + description: |- + nfs represents an NFS mount on the host that shares a pod's lifetime + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + properties: + path: + description: |- + path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + readOnly: + description: |- + readOnly here will force the NFS export to be mounted with read-only permissions. + Defaults to false. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: boolean + server: + description: |- + server is the hostname or IP address of the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: |- + persistentVolumeClaimVolumeSource represents a reference to a + PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + properties: + claimName: + description: |- + claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims + type: string + readOnly: + description: |- + readOnly Will force the ReadOnly setting in VolumeMounts. + Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: |- + photonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine. + Deprecated: PhotonPersistentDisk is deprecated and the in-tree photonPersistentDisk type is no longer supported. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + pdID: + description: pdID is the ID that identifies + Photon Controller persistent disk + type: string + required: + - pdID + type: object + portworxVolume: + description: |- + portworxVolume represents a portworx volume attached and mounted on kubelets host machine. + Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type + are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate + is on. + properties: + fsType: + description: |- + fSType represents the filesystem type to mount + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: volumeID uniquely identifies + a Portworx volume + type: string + required: + - volumeID + type: object + projected: + description: projected items for all in one + resources secrets, configmaps, and downward + API + properties: + defaultMode: + description: |- + defaultMode are the mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + sources: + description: |- + sources is the list of volume projections. Each entry in this list + handles one source. + items: + description: |- + Projection that may be projected along with other supported volume types. + Exactly one of these fields must be set. + properties: + clusterTrustBundle: + description: |- + ClusterTrustBundle allows a pod to access the `.spec.trustBundle` field + of ClusterTrustBundle objects in an auto-updating file. + + Alpha, gated by the ClusterTrustBundleProjection feature gate. + + ClusterTrustBundle objects can either be selected by name, or by the + combination of signer name and a label selector. + + Kubelet performs aggressive normalization of the PEM contents written + into the pod filesystem. Esoteric PEM features such as inter-block + comments and block headers are stripped. Certificates are deduplicated. + The ordering of certificates within the file is arbitrary, and Kubelet + may change the order over time. + properties: + labelSelector: + description: |- + Select all ClusterTrustBundles that match this label selector. Only has + effect if signerName is set. Mutually-exclusive with name. If unset, + interpreted as "match nothing". If set but empty, interpreted as "match + everything". + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + name: + description: |- + Select a single ClusterTrustBundle by object name. Mutually-exclusive + with signerName and labelSelector. + type: string + optional: + description: |- + If true, don't block pod startup if the referenced ClusterTrustBundle(s) + aren't available. If using name, then the named ClusterTrustBundle is + allowed not to exist. If using signerName, then the combination of + signerName and labelSelector is allowed to match zero + ClusterTrustBundles. + type: boolean + path: + description: Relative path from + the volume root to write the + bundle. + type: string + signerName: + description: |- + Select all ClusterTrustBundles that match this signer name. + Mutually-exclusive with name. The contents of all selected + ClusterTrustBundles will be unified and deduplicated. + type: string + required: + - path + type: object + configMap: + description: configMap information + about the configMap data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + ConfigMap will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the ConfigMap, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key + to a path within a volume. + properties: + key: + description: key is the + key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional specify + whether the ConfigMap or its + keys must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + downwardAPI: + description: downwardAPI information + about the downwardAPI data to project + properties: + items: + description: Items is a list of + DownwardAPIVolume file + items: + description: DownwardAPIVolumeFile + represents information to + create the file containing + the pod field + properties: + fieldRef: + description: 'Required: + Selects a field of the + pod: only annotations, + labels, name, namespace + and uid are supported.' + properties: + apiVersion: + description: Version + of the schema the + FieldPath is written + in terms of, defaults + to "v1". + type: string + fieldPath: + description: Path of + the field to select + in the specified API + version. + type: string + required: + - fieldPath + type: object + x-kubernetes-map-type: atomic + mode: + description: |- + Optional: mode bits used to set permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: 'Required: + Path is the relative + path name of the file + to be created. Must not + be absolute or contain + the ''..'' path. Must + be utf-8 encoded. The + first item of the relative + path must not start with + ''..''' + type: string + resourceFieldRef: + description: |- + Selects a resource of the container: only resources limits and requests + (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported. + properties: + containerName: + description: 'Container + name: required for + volumes, optional + for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies + the output format + of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: + resource to select' + type: string + required: + - resource + type: object + x-kubernetes-map-type: atomic + required: + - path + type: object + type: array + x-kubernetes-list-type: atomic + type: object + secret: + description: secret information about + the secret data to project + properties: + items: + description: |- + items if unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key + to a path within a volume. + properties: + key: + description: key is the + key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + optional: + description: optional field specify + whether the Secret or its key + must be defined + type: boolean + type: object + x-kubernetes-map-type: atomic + serviceAccountToken: + description: serviceAccountToken is + information about the serviceAccountToken + data to project + properties: + audience: + description: |- + audience is the intended audience of the token. A recipient of a token + must identify itself with an identifier specified in the audience of the + token, and otherwise should reject the token. The audience defaults to the + identifier of the apiserver. + type: string + expirationSeconds: + description: |- + expirationSeconds is the requested duration of validity of the service + account token. As the token approaches expiration, the kubelet volume + plugin will proactively rotate the service account token. The kubelet will + start trying to rotate the token if the token is older than 80 percent of + its time to live or if the token is older than 24 hours.Defaults to 1 hour + and must be at least 10 minutes. + format: int64 + type: integer + path: + description: |- + path is the path relative to the mount point of the file to project the + token into. + type: string + required: + - path + type: object + type: object + type: array + x-kubernetes-list-type: atomic + type: object + quobyte: + description: |- + quobyte represents a Quobyte mount on the host that shares a pod's lifetime. + Deprecated: Quobyte is deprecated and the in-tree quobyte type is no longer supported. + properties: + group: + description: |- + group to map volume access to + Default is no group + type: string + readOnly: + description: |- + readOnly here will force the Quobyte volume to be mounted with read-only permissions. + Defaults to false. + type: boolean + registry: + description: |- + registry represents a single or multiple Quobyte Registry services + specified as a string as host:port pair (multiple entries are separated with commas) + which acts as the central registry for volumes + type: string + tenant: + description: |- + tenant owning the given Quobyte volume in the Backend + Used with dynamically provisioned Quobyte volumes, value is set by the plugin + type: string + user: + description: |- + user to map volume access to + Defaults to serivceaccount user + type: string + volume: + description: volume is a string that references + an already created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: |- + rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. + Deprecated: RBD is deprecated and the in-tree rbd type is no longer supported. + More info: https://examples.k8s.io/volumes/rbd/README.md + properties: + fsType: + description: |- + fsType is the filesystem type of the volume that you want to mount. + Tip: Ensure that the filesystem type is supported by the host operating system. + Examples: "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + type: string + image: + description: |- + image is the rados image name. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + keyring: + default: /etc/ceph/keyring + description: |- + keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + monitors: + description: |- + monitors is a collection of Ceph monitors. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + items: + type: string + type: array + x-kubernetes-list-type: atomic + pool: + default: rbd + description: |- + pool is the rados pool name. + Default is rbd. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + readOnly: + description: |- + readOnly here will force the ReadOnly setting in VolumeMounts. + Defaults to false. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: boolean + secretRef: + description: |- + secretRef is name of the authentication secret for RBDUser. If provided + overrides keyring. + Default is nil. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + user: + default: admin + description: |- + user is the rados user name. + Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it + type: string + required: + - image + - monitors + type: object + scaleIO: + description: |- + scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. + Deprecated: ScaleIO is deprecated and the in-tree scaleIO type is no longer supported. + properties: + fsType: + default: xfs + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". + Default is "xfs". + type: string + gateway: + description: gateway is the host address + of the ScaleIO API Gateway. + type: string + protectionDomain: + description: protectionDomain is the name + of the ScaleIO Protection Domain for the + configured storage. + type: string + readOnly: + description: |- + readOnly Defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef references to the secret for ScaleIO user and other + sensitive information. If this is not provided, Login operation will fail. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + sslEnabled: + description: sslEnabled Flag enable/disable + SSL communication with Gateway, default + false + type: boolean + storageMode: + default: ThinProvisioned + description: |- + storageMode indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. + Default is ThinProvisioned. + type: string + storagePool: + description: storagePool is the ScaleIO + Storage Pool associated with the protection + domain. + type: string + system: + description: system is the name of the storage + system as configured in ScaleIO. + type: string + volumeName: + description: |- + volumeName is the name of a volume already created in the ScaleIO system + that is associated with this volume source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: |- + secret represents a secret that should populate this volume. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + properties: + defaultMode: + description: |- + defaultMode is Optional: mode bits used to set permissions on created files by default. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values + for mode bits. Defaults to 0644. + Directories within the path are not affected by this setting. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + items: + description: |- + items If unspecified, each key-value pair in the Data field of the referenced + Secret will be projected into the volume as a file whose name is the + key and content is the value. If specified, the listed keys will be + projected into the specified paths, and unlisted keys will not be + present. If a key is specified which is not present in the Secret, + the volume setup will error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: key is the key to project. + type: string + mode: + description: |- + mode is Optional: mode bits used to set permissions on this file. + Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. + YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. + If not specified, the volume defaultMode will be used. + This might be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode bits set. + format: int32 + type: integer + path: + description: |- + path is the relative path of the file to map the key to. + May not be an absolute path. + May not contain the path element '..'. + May not start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + x-kubernetes-list-type: atomic + optional: + description: optional field specify whether + the Secret or its keys must be defined + type: boolean + secretName: + description: |- + secretName is the name of the secret in the pod's namespace to use. + More info: https://kubernetes.io/docs/concepts/storage/volumes#secret + type: string + type: object + storageos: + description: |- + storageOS represents a StorageOS volume attached and mounted on Kubernetes nodes. + Deprecated: StorageOS is deprecated and the in-tree storageos type is no longer supported. + properties: + fsType: + description: |- + fsType is the filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: |- + readOnly defaults to false (read/write). ReadOnly here will force + the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: |- + secretRef specifies the secret to use for obtaining the StorageOS API + credentials. If not specified, default values will be attempted. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + volumeName: + description: |- + volumeName is the human-readable name of the StorageOS volume. Volume + names are only unique within a namespace. + type: string + volumeNamespace: + description: |- + volumeNamespace specifies the scope of the volume within StorageOS. If no + namespace is specified then the Pod's namespace will be used. This allows the + Kubernetes name scoping to be mirrored within StorageOS for tighter integration. + Set VolumeName to any name to override the default behaviour. + Set to "default" if you are not using namespaces within StorageOS. + Namespaces that do not pre-exist within StorageOS will be created. + type: string + type: object + vsphereVolume: + description: |- + vsphereVolume represents a vSphere volume attached and mounted on kubelets host machine. + Deprecated: VsphereVolume is deprecated. All operations for the in-tree vsphereVolume type + are redirected to the csi.vsphere.vmware.com CSI driver. + properties: + fsType: + description: |- + fsType is filesystem type to mount. + Must be a filesystem type supported by the host operating system. + Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + storagePolicyID: + description: storagePolicyID is the storage + Policy Based Management (SPBM) profile + ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: storagePolicyName is the storage + Policy Based Management (SPBM) profile + name. + type: string + volumePath: + description: volumePath is the path that + identifies vSphere volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + type: object + replicas: + description: Replicas is the number of desired pods. Defaults + to 1. + format: int32 + type: integer + strategy: + description: The deployment strategy to use to replace + existing pods with new ones. + properties: + rollingUpdate: + description: |- + Rolling update config params. Present only if DeploymentStrategyType = + RollingUpdate. + properties: + maxSurge: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be scheduled above the desired number of + pods. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + This can not be 0 if MaxUnavailable is 0. + Absolute number is calculated from percentage by rounding up. + Defaults to 25%. + Example: when this is set to 30%, the new ReplicaSet can be scaled up immediately when + the rolling update starts, such that the total number of old and new pods do not exceed + 130% of desired pods. Once old pods have been killed, + new ReplicaSet can be scaled up further, ensuring that total number of pods running + at any time during the update is at most 130% of desired pods. + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + The maximum number of pods that can be unavailable during the update. + Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). + Absolute number is calculated from percentage by rounding down. + This can not be 0 if MaxSurge is 0. + Defaults to 25%. + Example: when this is set to 30%, the old ReplicaSet can be scaled down to 70% of desired pods + immediately when the rolling update starts. Once new pods are ready, old ReplicaSet + can be scaled down further, followed by scaling up the new ReplicaSet, ensuring + that the total number of pods available at all times during the update is at + least 70% of desired pods. + x-kubernetes-int-or-string: true + type: object + type: + description: Type of deployment. Can be "Recreate" + or "RollingUpdate". Default is RollingUpdate. + type: string + type: object + type: object + envoyHpa: + description: EnvoyHpa defines the Horizontal Pod Autoscaler + settings for Envoy Proxy Deployment. + properties: + behavior: + description: |- + behavior configures the scaling behavior of the target + in both Up and Down directions (scaleUp and scaleDown fields respectively). + If not set, the default HPAScalingRules for scale up and scale down are used. + See k8s.io.autoscaling.v2.HorizontalPodAutoScalerBehavior. + properties: + scaleDown: + description: |- + scaleDown is scaling policy for scaling Down. + If not set, the default value is to allow to scale down to minReplicas pods, with a + 300 second stabilization window (i.e., the highest recommendation for + the last 300sec is used). + properties: + policies: + description: |- + policies is a list of potential scaling polices which can be used during scaling. + If not set, use the default values: + - For scale up: allow doubling the number of pods, or an absolute change of 4 pods in a 15s window. + - For scale down: allow all pods to be removed in a 15s window. + items: + description: HPAScalingPolicy is a single policy + which must hold true for a specified past + interval. + properties: + periodSeconds: + description: |- + periodSeconds specifies the window of time for which the policy should hold true. + PeriodSeconds must be greater than zero and less than or equal to 1800 (30 min). + format: int32 + type: integer + type: + description: type is used to specify the + scaling policy. + type: string + value: + description: |- + value contains the amount of change which is permitted by the policy. + It must be greater than zero + format: int32 + type: integer + required: + - periodSeconds + - type + - value + type: object + type: array + x-kubernetes-list-type: atomic + selectPolicy: + description: |- + selectPolicy is used to specify which policy should be used. + If not set, the default value Max is used. + type: string + stabilizationWindowSeconds: + description: |- + stabilizationWindowSeconds is the number of seconds for which past recommendations should be + considered while scaling up or scaling down. + StabilizationWindowSeconds must be greater than or equal to zero and less than or equal to 3600 (one hour). + If not set, use the default values: + - For scale up: 0 (i.e. no stabilization is done). + - For scale down: 300 (i.e. the stabilization window is 300 seconds long). + format: int32 + type: integer + tolerance: + anyOf: + - type: integer + - type: string + description: |- + tolerance is the tolerance on the ratio between the current and desired + metric value under which no updates are made to the desired number of + replicas (e.g. 0.01 for 1%). Must be greater than or equal to zero. If not + set, the default cluster-wide tolerance is applied (by default 10%). + + For example, if autoscaling is configured with a memory consumption target of 100Mi, + and scale-down and scale-up tolerances of 5% and 1% respectively, scaling will be + triggered when the actual consumption falls below 95Mi or exceeds 101Mi. + + This is an alpha field and requires enabling the HPAConfigurableTolerance + feature gate. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + scaleUp: + description: |- + scaleUp is scaling policy for scaling Up. + If not set, the default value is the higher of: + * increase no more than 4 pods per 60 seconds + * double the number of pods per 60 seconds + No stabilization is used. + properties: + policies: + description: |- + policies is a list of potential scaling polices which can be used during scaling. + If not set, use the default values: + - For scale up: allow doubling the number of pods, or an absolute change of 4 pods in a 15s window. + - For scale down: allow all pods to be removed in a 15s window. + items: + description: HPAScalingPolicy is a single policy + which must hold true for a specified past + interval. + properties: + periodSeconds: + description: |- + periodSeconds specifies the window of time for which the policy should hold true. + PeriodSeconds must be greater than zero and less than or equal to 1800 (30 min). + format: int32 + type: integer + type: + description: type is used to specify the + scaling policy. + type: string + value: + description: |- + value contains the amount of change which is permitted by the policy. + It must be greater than zero + format: int32 + type: integer + required: + - periodSeconds + - type + - value + type: object + type: array + x-kubernetes-list-type: atomic + selectPolicy: + description: |- + selectPolicy is used to specify which policy should be used. + If not set, the default value Max is used. + type: string + stabilizationWindowSeconds: + description: |- + stabilizationWindowSeconds is the number of seconds for which past recommendations should be + considered while scaling up or scaling down. + StabilizationWindowSeconds must be greater than or equal to zero and less than or equal to 3600 (one hour). + If not set, use the default values: + - For scale up: 0 (i.e. no stabilization is done). + - For scale down: 300 (i.e. the stabilization window is 300 seconds long). + format: int32 + type: integer + tolerance: + anyOf: + - type: integer + - type: string + description: |- + tolerance is the tolerance on the ratio between the current and desired + metric value under which no updates are made to the desired number of + replicas (e.g. 0.01 for 1%). Must be greater than or equal to zero. If not + set, the default cluster-wide tolerance is applied (by default 10%). + + For example, if autoscaling is configured with a memory consumption target of 100Mi, + and scale-down and scale-up tolerances of 5% and 1% respectively, scaling will be + triggered when the actual consumption falls below 95Mi or exceeds 101Mi. + + This is an alpha field and requires enabling the HPAConfigurableTolerance + feature gate. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + type: object + maxReplicas: + description: |- + maxReplicas is the upper limit for the number of replicas to which the autoscaler can scale up. + It cannot be less that minReplicas. + format: int32 + type: integer + x-kubernetes-validations: + - message: maxReplicas must be greater than 0 + rule: self > 0 + metrics: + description: |- + metrics contains the specifications for which to use to calculate the + desired replica count (the maximum replica count across all metrics will + be used). + If left empty, it defaults to being based on CPU utilization with average on 80% usage. + items: + description: |- + MetricSpec specifies how to scale based on a single metric + (only `type` and one other matching field should be set at once). + properties: + containerResource: + description: |- + containerResource refers to a resource metric (such as those specified in + requests and limits) known to Kubernetes describing a single container in + each pod of the current scale target (e.g. CPU or memory). Such metrics are + built in to Kubernetes, and have special scaling options on top of those + available to normal per-pod metrics using the "pods" source. + properties: + container: + description: container is the name of the container + in the pods of the scaling target + type: string + name: + description: name is the name of the resource + in question. + type: string + target: + description: target specifies the target value + for the given metric + properties: + averageUtilization: + description: |- + averageUtilization is the target value of the average of the + resource metric across all relevant pods, represented as a percentage of + the requested value of the resource for the pods. + Currently only valid for Resource metric source type + format: int32 + type: integer + averageValue: + anyOf: + - type: integer + - type: string + description: |- + averageValue is the target value of the average of the + metric across all relevant pods (as a quantity) + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: + description: type represents whether the + metric type is Utilization, Value, or + AverageValue + type: string + value: + anyOf: + - type: integer + - type: string + description: value is the target value of + the metric (as a quantity). + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - type + type: object + required: + - container + - name + - target + type: object + external: + description: |- + external refers to a global metric that is not associated + with any Kubernetes object. It allows autoscaling based on information + coming from components running outside of cluster + (for example length of queue in cloud messaging service, or + QPS from loadbalancer running outside of cluster). + properties: + metric: + description: metric identifies the target metric + by name and selector + properties: + name: + description: name is the name of the given + metric + type: string + selector: + description: |- + selector is the string-encoded form of a standard kubernetes label selector for the given metric + When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping. + When unset, just the metricName will be used to gather metrics. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + required: + - name + type: object + target: + description: target specifies the target value + for the given metric + properties: + averageUtilization: + description: |- + averageUtilization is the target value of the average of the + resource metric across all relevant pods, represented as a percentage of + the requested value of the resource for the pods. + Currently only valid for Resource metric source type + format: int32 + type: integer + averageValue: + anyOf: + - type: integer + - type: string + description: |- + averageValue is the target value of the average of the + metric across all relevant pods (as a quantity) + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: + description: type represents whether the + metric type is Utilization, Value, or + AverageValue + type: string + value: + anyOf: + - type: integer + - type: string + description: value is the target value of + the metric (as a quantity). + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - type + type: object + required: + - metric + - target + type: object + object: + description: |- + object refers to a metric describing a single kubernetes object + (for example, hits-per-second on an Ingress object). + properties: + describedObject: + description: describedObject specifies the descriptions + of a object,such as kind,name apiVersion + properties: + apiVersion: + description: apiVersion is the API version + of the referent + type: string + kind: + description: 'kind is the kind of the referent; + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'name is the name of the referent; + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + required: + - kind + - name + type: object + metric: + description: metric identifies the target metric + by name and selector + properties: + name: + description: name is the name of the given + metric + type: string + selector: + description: |- + selector is the string-encoded form of a standard kubernetes label selector for the given metric + When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping. + When unset, just the metricName will be used to gather metrics. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + required: + - name + type: object + target: + description: target specifies the target value + for the given metric + properties: + averageUtilization: + description: |- + averageUtilization is the target value of the average of the + resource metric across all relevant pods, represented as a percentage of + the requested value of the resource for the pods. + Currently only valid for Resource metric source type + format: int32 + type: integer + averageValue: + anyOf: + - type: integer + - type: string + description: |- + averageValue is the target value of the average of the + metric across all relevant pods (as a quantity) + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: + description: type represents whether the + metric type is Utilization, Value, or + AverageValue + type: string + value: + anyOf: + - type: integer + - type: string + description: value is the target value of + the metric (as a quantity). + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - type + type: object + required: + - describedObject + - metric + - target + type: object + pods: + description: |- + pods refers to a metric describing each pod in the current scale target + (for example, transactions-processed-per-second). The values will be + averaged together before being compared to the target value. + properties: + metric: + description: metric identifies the target metric + by name and selector + properties: + name: + description: name is the name of the given + metric + type: string + selector: + description: |- + selector is the string-encoded form of a standard kubernetes label selector for the given metric + When set, it is passed as an additional parameter to the metrics server for more specific metrics scoping. + When unset, just the metricName will be used to gather metrics. + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + required: + - name + type: object + target: + description: target specifies the target value + for the given metric + properties: + averageUtilization: + description: |- + averageUtilization is the target value of the average of the + resource metric across all relevant pods, represented as a percentage of + the requested value of the resource for the pods. + Currently only valid for Resource metric source type + format: int32 + type: integer + averageValue: + anyOf: + - type: integer + - type: string + description: |- + averageValue is the target value of the average of the + metric across all relevant pods (as a quantity) + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: + description: type represents whether the + metric type is Utilization, Value, or + AverageValue + type: string + value: + anyOf: + - type: integer + - type: string + description: value is the target value of + the metric (as a quantity). + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - type + type: object + required: + - metric + - target + type: object + resource: + description: |- + resource refers to a resource metric (such as those specified in + requests and limits) known to Kubernetes describing each pod in the + current scale target (e.g. CPU or memory). Such metrics are built in to + Kubernetes, and have special scaling options on top of those available + to normal per-pod metrics using the "pods" source. + properties: + name: + description: name is the name of the resource + in question. + type: string + target: + description: target specifies the target value + for the given metric + properties: + averageUtilization: + description: |- + averageUtilization is the target value of the average of the + resource metric across all relevant pods, represented as a percentage of + the requested value of the resource for the pods. + Currently only valid for Resource metric source type + format: int32 + type: integer + averageValue: + anyOf: + - type: integer + - type: string + description: |- + averageValue is the target value of the average of the + metric across all relevant pods (as a quantity) + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: + description: type represents whether the + metric type is Utilization, Value, or + AverageValue + type: string + value: + anyOf: + - type: integer + - type: string + description: value is the target value of + the metric (as a quantity). + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - type + type: object + required: + - name + - target + type: object + type: + description: |- + type is the type of metric source. It should be one of "ContainerResource", "External", + "Object", "Pods" or "Resource", each mapping to a matching field in the object. + type: string + required: + - type + type: object + type: array + minReplicas: + description: |- + minReplicas is the lower limit for the number of replicas to which the autoscaler + can scale down. It defaults to 1 replica. + format: int32 + type: integer + x-kubernetes-validations: + - message: minReplicas must be greater than 0 + rule: self > 0 + patch: + description: Patch defines how to perform the patch operation + to the HorizontalPodAutoscaler + properties: + type: + description: |- + Type is the type of merge operation to perform + + By default, StrategicMerge is used as the patch type. + type: string + value: + description: Object contains the raw configuration + for merged object + x-kubernetes-preserve-unknown-fields: true + required: + - value + type: object + required: + - maxReplicas + type: object + x-kubernetes-validations: + - message: maxReplicas cannot be less than minReplicas + rule: '!has(self.minReplicas) || self.maxReplicas >= self.minReplicas' + envoyPDB: + description: EnvoyPDB allows to control the pod disruption + budget of an Envoy Proxy. + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + description: |- + MaxUnavailable specifies the maximum amount of pods (can be expressed as integers or as a percentage) that can be unavailable at all times during voluntary disruptions, + such as node drains or updates. This setting ensures that your envoy proxy maintains a certain level of availability + and resilience during maintenance operations. Cannot be combined with minAvailable. + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + description: |- + MinAvailable specifies the minimum amount of pods (can be expressed as integers or as a percentage) that must be available at all times during voluntary disruptions, + such as node drains or updates. This setting ensures that your envoy proxy maintains a certain level of availability + and resilience during maintenance operations. Cannot be combined with maxUnavailable. + x-kubernetes-int-or-string: true + patch: + description: Patch defines how to perform the patch operation + to the PodDisruptionBudget + properties: + type: + description: |- + Type is the type of merge operation to perform + + By default, StrategicMerge is used as the patch type. + type: string + value: + description: Object contains the raw configuration + for merged object + x-kubernetes-preserve-unknown-fields: true + required: + - value + type: object + type: object + x-kubernetes-validations: + - message: only one of minAvailable or maxUnavailable can + be specified + rule: (has(self.minAvailable) && !has(self.maxUnavailable)) + || (!has(self.minAvailable) && has(self.maxUnavailable)) + envoyService: + description: |- + EnvoyService defines the desired state of the Envoy service resource. + If unspecified, default settings for the managed Envoy service resource + are applied. + properties: + allocateLoadBalancerNodePorts: + description: |- + AllocateLoadBalancerNodePorts defines if NodePorts will be automatically allocated for + services with type LoadBalancer. Default is "true". It may be set to "false" if the cluster + load-balancer does not rely on NodePorts. If the caller requests specific NodePorts (by specifying a + value), those requests will be respected, regardless of this field. This field may only be set for + services with type LoadBalancer and will be cleared if the type is changed to any other type. + type: boolean + annotations: + additionalProperties: + type: string + description: |- + Annotations that should be appended to the service. + By default, no annotations are appended. + type: object + externalTrafficPolicy: + default: Local + description: |- + ExternalTrafficPolicy determines the externalTrafficPolicy for the Envoy Service. Valid options + are Local and Cluster. Default is "Local". "Local" means traffic will only go to pods on the node + receiving the traffic. "Cluster" means connections are loadbalanced to all pods in the cluster. + enum: + - Local + - Cluster + type: string + labels: + additionalProperties: + type: string + description: |- + Labels that should be appended to the service. + By default, no labels are appended. + type: object + loadBalancerClass: + description: |- + LoadBalancerClass, when specified, allows for choosing the LoadBalancer provider + implementation if more than one are available or is otherwise expected to be specified + type: string + loadBalancerIP: + description: |- + LoadBalancerIP defines the IP Address of the underlying load balancer service. This field + may be ignored if the load balancer provider does not support this feature. + This field has been deprecated in Kubernetes, but it is still used for setting the IP Address in some cloud + providers such as GCP. + type: string + x-kubernetes-validations: + - message: loadBalancerIP must be a valid IPv4 address + rule: self.matches(r"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$") + loadBalancerSourceRanges: + description: |- + LoadBalancerSourceRanges defines a list of allowed IP addresses which will be configured as + firewall rules on the platform providers load balancer. This is not guaranteed to be working as + it happens outside of kubernetes and has to be supported and handled by the platform provider. + This field may only be set for services with type LoadBalancer and will be cleared if the type + is changed to any other type. + items: + type: string + type: array + name: + description: |- + Name of the service. + When unset, this defaults to an autogenerated name. + type: string + patch: + description: Patch defines how to perform the patch operation + to the service + properties: + type: + description: |- + Type is the type of merge operation to perform + + By default, StrategicMerge is used as the patch type. + type: string + value: + description: Object contains the raw configuration + for merged object + x-kubernetes-preserve-unknown-fields: true + required: + - value + type: object + type: + default: LoadBalancer + description: |- + Type determines how the Service is exposed. Defaults to LoadBalancer. + Valid options are ClusterIP, LoadBalancer and NodePort. + "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. + "NodePort" means a service will be exposed on a static Port on all Nodes of the cluster. + enum: + - ClusterIP + - LoadBalancer + - NodePort + type: string + type: object + x-kubernetes-validations: + - message: allocateLoadBalancerNodePorts can only be set for + LoadBalancer type + rule: '!has(self.allocateLoadBalancerNodePorts) || self.type + == ''LoadBalancer''' + - message: loadBalancerSourceRanges can only be set for LoadBalancer + type + rule: '!has(self.loadBalancerSourceRanges) || self.type + == ''LoadBalancer''' + - message: loadBalancerIP can only be set for LoadBalancer + type + rule: '!has(self.loadBalancerIP) || self.type == ''LoadBalancer''' + useListenerPortAsContainerPort: + description: |- + UseListenerPortAsContainerPort disables the port shifting feature in the Envoy Proxy. + When set to false (default value), if the service port is a privileged port (1-1023), add a constant to the value converting it into an ephemeral port. + This allows the container to bind to the port without needing a CAP_NET_BIND_SERVICE capability. + type: boolean + type: object + x-kubernetes-validations: + - message: only one of envoyDeployment or envoyDaemonSet can be + specified + rule: ((has(self.envoyDeployment) && !has(self.envoyDaemonSet)) + || (!has(self.envoyDeployment) && has(self.envoyDaemonSet))) + || (!has(self.envoyDeployment) && !has(self.envoyDaemonSet)) + - message: cannot use envoyHpa if envoyDaemonSet is used + rule: ((has(self.envoyHpa) && !has(self.envoyDaemonSet)) || + (!has(self.envoyHpa) && has(self.envoyDaemonSet))) || (!has(self.envoyHpa) + && !has(self.envoyDaemonSet)) + type: + description: |- + 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". + enum: + - Kubernetes + - Custom + type: string + required: + - type + type: object + routingType: + description: |- + RoutingType can be set to "Service" to use the Service Cluster IP for routing to the backend, + or it can be set to "Endpoint" to use Endpoint routing. The default is "Endpoint". + type: string + shutdown: + description: Shutdown defines configuration for graceful envoy shutdown + process. + properties: + drainTimeout: + description: |- + DrainTimeout defines the graceful drain timeout. This should be less than the pod's terminationGracePeriodSeconds. + If unspecified, defaults to 60 seconds. + type: string + minDrainDuration: + description: |- + MinDrainDuration defines the minimum drain duration allowing time for endpoint deprogramming to complete. + If unspecified, defaults to 10 seconds. + type: string + type: object + telemetry: + description: Telemetry defines telemetry parameters for managed proxies. + properties: + accessLog: + description: |- + AccessLogs defines accesslog parameters for managed proxies. + If unspecified, will send default format to stdout. + properties: + disable: + description: Disable disables access logging for managed proxies + if set to true. + type: boolean + settings: + description: |- + Settings defines accesslog settings for managed proxies. + If unspecified, will send default format to stdout. + items: + properties: + format: + description: |- + Format defines the format of accesslog. + This will be ignored if sink type is ALS. + properties: + json: + additionalProperties: + type: string + description: |- + JSON is additional attributes that describe the specific event occurrence. + Structured format for the envoy access logs. Envoy [command operators](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#command-operators) + can be used as values for fields within the Struct. + It's required when the format type is "JSON". + type: object + text: + description: |- + Text defines the text accesslog format, following Envoy accesslog formatting, + It's required when the format type is "Text". + Envoy [command operators](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#command-operators) may be used in the format. + The [format string documentation](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#config-access-log-format-strings) provides more information. + type: string + type: + description: Type defines the type of accesslog + format. + enum: + - Text + - JSON + type: string + type: object + x-kubernetes-validations: + - message: If AccessLogFormat type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) : !has(self.text)' + - message: If AccessLogFormat type is JSON, json field + needs to be set. + rule: 'self.type == ''JSON'' ? has(self.json) : !has(self.json)' + matches: + description: |- + Matches defines the match conditions for accesslog in CEL expression. + An accesslog will be emitted only when one or more match conditions are evaluated to true. + Invalid [CEL](https://www.envoyproxy.io/docs/envoy/latest/xds/type/v3/cel.proto.html#common-expression-language-cel-proto) expressions will be ignored. + items: + type: string + maxItems: 10 + type: array + sinks: + description: Sinks defines the sinks of accesslog. + items: + description: ProxyAccessLogSink defines the sink of + accesslog. + properties: + als: + description: ALS defines the gRPC Access Log Service + (ALS) sink. + properties: + backendRef: + description: |- + BackendRef references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + + Deprecated: Use BackendRefs instead. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + backendRefs: + description: |- + BackendRefs references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + items: + description: BackendRef defines how an ObjectReference + that is specific to BackendRef. + properties: + fallback: + description: |- + Fallback indicates whether the backend is designated as a fallback. + Multiple fallback backends can be configured. + It is highly recommended to configure active or passive health checks to ensure that failover can be detected + when the active backends become unhealthy and to automatically readjust once the primary backends are healthy again. + The overprovisioning factor is set to 1.4, meaning the fallback backends will only start receiving traffic when + the health of the active backends falls below 72%. + type: boolean + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the + referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + maxItems: 16 + type: array + backendSettings: + description: |- + BackendSettings holds configuration for managing the connection + to the backend. + properties: + circuitBreaker: + description: |- + Circuit Breaker settings for the upstream connections and requests. + If not set, circuit breakers will be enabled with the default thresholds + properties: + maxConnections: + default: 1024 + description: The maximum number of + connections that Envoy will establish + to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRequests: + default: 1024 + description: The maximum number of + parallel requests that Envoy will + make to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRetries: + default: 1024 + description: The maximum number of + parallel retries that Envoy will + make to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxPendingRequests: + default: 1024 + description: The maximum number of + pending requests that Envoy will + queue to the referenced backend + defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxRequestsPerConnection: + description: |- + The maximum number of requests that Envoy will make over a single connection to the referenced backend defined within a xRoute rule. + Default: unlimited. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + perEndpoint: + description: PerEndpoint defines Circuit + Breakers that will apply per-endpoint + for an upstream cluster + properties: + maxConnections: + default: 1024 + description: MaxConnections configures + the maximum number of connections + that Envoy will establish per-endpoint + to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + type: object + type: object + connection: + description: Connection includes backend + connection settings. + properties: + bufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + BufferLimit Soft limit on size of the cluster’s connections read and write buffers. + BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space. + If unspecified, an implementation defined default is applied (32768 bytes). + For example, 20Mi, 1Gi, 256Ki etc. + Note: that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + socketBufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + SocketBufferLimit provides configuration for the maximum buffer size in bytes for each socket + to backend. + SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space. + For example, 20Mi, 1Gi, 256Ki etc. + Note that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + type: object + dns: + description: DNS includes dns resolution + settings. + properties: + dnsRefreshRate: + description: |- + DNSRefreshRate specifies the rate at which DNS records should be refreshed. + Defaults to 30 seconds. + type: string + lookupFamily: + description: |- + LookupFamily determines how Envoy would resolve DNS for Routes where the backend is specified as a fully qualified domain name (FQDN). + If set, this configuration overrides other defaults. + enum: + - IPv4 + - IPv6 + - IPv4Preferred + - IPv6Preferred + - IPv4AndIPv6 + type: string + respectDnsTtl: + description: |- + RespectDNSTTL indicates whether the DNS Time-To-Live (TTL) should be respected. + If the value is set to true, the DNS refresh rate will be set to the resource record’s TTL. + Defaults to true. + type: boolean + type: object + healthCheck: + description: HealthCheck allows gateway + to perform active health checking on + backends. + properties: + active: + description: Active health check configuration + properties: + grpc: + description: |- + GRPC defines the configuration of the GRPC health checker. + It's optional, and can only be used if the specified type is GRPC. + properties: + service: + description: |- + Service to send in the health check request. + If this is not specified, then the health check request applies to the entire + server and not to a specific service. + type: string + type: object + healthyThreshold: + default: 1 + description: HealthyThreshold + defines the number of healthy + health checks required before + a backend host is marked healthy. + format: int32 + minimum: 1 + type: integer + http: + description: |- + HTTP defines the configuration of http health checker. + It's required while the health checker type is HTTP. + properties: + expectedResponse: + description: ExpectedResponse + defines a list of HTTP expected + responses to match. + properties: + binary: + description: Binary payload + base64 encoded. + format: byte + type: string + text: + description: Text payload + in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines + the type of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type + is Text, text field needs + to be set. + rule: 'self.type == ''Text'' + ? has(self.text) : !has(self.text)' + - message: If payload type + is Binary, binary field + needs to be set. + rule: 'self.type == ''Binary'' + ? has(self.binary) : !has(self.binary)' + expectedStatuses: + description: |- + ExpectedStatuses defines a list of HTTP response statuses considered healthy. + Defaults to 200 only + items: + description: HTTPStatus + defines the http status + code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + method: + description: |- + Method defines the HTTP method used for health checking. + Defaults to GET + type: string + path: + description: Path defines + the HTTP path that will + be requested during health + checking. + maxLength: 1024 + minLength: 1 + type: string + required: + - path + type: object + interval: + default: 3s + description: Interval defines + the time between active health + checks. + format: duration + type: string + tcp: + description: |- + TCP defines the configuration of tcp health checker. + It's required while the health checker type is TCP. + properties: + receive: + description: Receive defines + the expected response payload. + properties: + binary: + description: Binary payload + base64 encoded. + format: byte + type: string + text: + description: Text payload + in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines + the type of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type + is Text, text field needs + to be set. + rule: 'self.type == ''Text'' + ? has(self.text) : !has(self.text)' + - message: If payload type + is Binary, binary field + needs to be set. + rule: 'self.type == ''Binary'' + ? has(self.binary) : !has(self.binary)' + send: + description: Send defines + the request payload. + properties: + binary: + description: Binary payload + base64 encoded. + format: byte + type: string + text: + description: Text payload + in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines + the type of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type + is Text, text field needs + to be set. + rule: 'self.type == ''Text'' + ? has(self.text) : !has(self.text)' + - message: If payload type + is Binary, binary field + needs to be set. + rule: 'self.type == ''Binary'' + ? has(self.binary) : !has(self.binary)' + type: object + timeout: + default: 1s + description: Timeout defines the + time to wait for a health check + response. + format: duration + type: string + type: + allOf: + - enum: + - HTTP + - TCP + - GRPC + - enum: + - HTTP + - TCP + - GRPC + description: Type defines the + type of health checker. + type: string + unhealthyThreshold: + default: 3 + description: UnhealthyThreshold + defines the number of unhealthy + health checks required before + a backend host is marked unhealthy. + format: int32 + minimum: 1 + type: integer + required: + - type + type: object + x-kubernetes-validations: + - message: If Health Checker type + is HTTP, http field needs to be + set. + rule: 'self.type == ''HTTP'' ? has(self.http) + : !has(self.http)' + - message: If Health Checker type + is TCP, tcp field needs to be + set. + rule: 'self.type == ''TCP'' ? has(self.tcp) + : !has(self.tcp)' + - message: The grpc field can only + be set if the Health Checker type + is GRPC. + rule: 'has(self.grpc) ? self.type + == ''GRPC'' : true' + panicThreshold: + description: |- + When number of unhealthy endpoints for a backend reaches this threshold + Envoy will disregard health status and balance across all endpoints. + It's designed to prevent a situation in which host failures cascade throughout the cluster + as load increases. If not set, the default value is 50%. To disable panic mode, set value to `0`. + format: int32 + maximum: 100 + minimum: 0 + type: integer + passive: + description: Passive passive check + configuration + properties: + baseEjectionTime: + default: 30s + description: BaseEjectionTime + defines the base duration for + which a host will be ejected + on consecutive failures. + format: duration + type: string + consecutive5XxErrors: + default: 5 + description: Consecutive5xxErrors + sets the number of consecutive + 5xx errors triggering ejection. + format: int32 + type: integer + consecutiveGatewayErrors: + default: 0 + description: ConsecutiveGatewayErrors + sets the number of consecutive + gateway errors triggering ejection. + format: int32 + type: integer + consecutiveLocalOriginFailures: + default: 5 + description: |- + ConsecutiveLocalOriginFailures sets the number of consecutive local origin failures triggering ejection. + Parameter takes effect only when split_external_local_origin_errors is set to true. + format: int32 + type: integer + interval: + default: 3s + description: Interval defines + the time between passive health + checks. + format: duration + type: string + maxEjectionPercent: + default: 10 + description: MaxEjectionPercent + sets the maximum percentage + of hosts in a cluster that can + be ejected. + format: int32 + type: integer + splitExternalLocalOriginErrors: + default: false + description: SplitExternalLocalOriginErrors + enables splitting of errors + between external and local origin. + type: boolean + type: object + type: object + http2: + description: HTTP2 provides HTTP/2 configuration + for backend connections. + properties: + initialConnectionWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialConnectionWindowSize sets the initial window size for HTTP/2 connections. + If not set, the default value is 1 MiB. + x-kubernetes-int-or-string: true + initialStreamWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialStreamWindowSize sets the initial window size for HTTP/2 streams. + If not set, the default value is 64 KiB(64*1024). + x-kubernetes-int-or-string: true + maxConcurrentStreams: + description: |- + MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection. + If not set, the default value is 100. + format: int32 + maximum: 2147483647 + minimum: 1 + type: integer + onInvalidMessage: + description: |- + OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error + It's recommended for L2 Envoy deployments to set this value to TerminateStream. + https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two + Default: TerminateConnection + type: string + type: object + loadBalancer: + description: |- + LoadBalancer policy to apply when routing traffic from the gateway to + the backend endpoints. Defaults to `LeastRequest`. + properties: + consistentHash: + description: |- + ConsistentHash defines the configuration when the load balancer type is + set to ConsistentHash + properties: + cookie: + description: Cookie configures + the cookie hash policy when + the consistent hash type is + set to Cookie. + properties: + attributes: + additionalProperties: + type: string + description: Additional Attributes + to set for the generated + cookie. + type: object + name: + description: |- + Name of the cookie to hash. + If this cookie does not exist in the request, Envoy will generate a cookie and set + the TTL on the response back to the client based on Layer 4 + attributes of the backend endpoint, to ensure that these future requests + go to the same backend endpoint. Make sure to set the TTL field for this case. + type: string + ttl: + description: |- + TTL of the generated cookie if the cookie is not present. This value sets the + Max-Age attribute value. + type: string + required: + - name + type: object + header: + description: Header configures + the header hash policy when + the consistent hash type is + set to Header. + properties: + name: + description: Name of the header + to hash. + type: string + required: + - name + type: object + tableSize: + default: 65537 + description: The table size for + consistent hashing, must be + prime number limited to 5000011. + format: int64 + maximum: 5000011 + minimum: 2 + type: integer + type: + description: |- + ConsistentHashType defines the type of input to hash on. Valid Type values are + "SourceIP", + "Header", + "Cookie". + enum: + - SourceIP + - Header + - Cookie + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If consistent hash type + is header, the header field must + be set. + rule: 'self.type == ''Header'' ? + has(self.header) : !has(self.header)' + - message: If consistent hash type + is cookie, the cookie field must + be set. + rule: 'self.type == ''Cookie'' ? + has(self.cookie) : !has(self.cookie)' + slowStart: + description: |- + SlowStart defines the configuration related to the slow start load balancer policy. + If set, during slow start window, traffic sent to the newly added hosts will gradually increase. + Currently this is only supported for RoundRobin and LeastRequest load balancers + properties: + window: + description: |- + Window defines the duration of the warm up period for newly added host. + During slow start window, traffic sent to the newly added hosts will gradually increase. + Currently only supports linear growth of traffic. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#config-cluster-v3-cluster-slowstartconfig + type: string + required: + - window + type: object + type: + description: |- + Type decides the type of Load Balancer policy. + Valid LoadBalancerType values are + "ConsistentHash", + "LeastRequest", + "Random", + "RoundRobin". + enum: + - ConsistentHash + - LeastRequest + - Random + - RoundRobin + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If LoadBalancer type is consistentHash, + consistentHash field needs to be set. + rule: 'self.type == ''ConsistentHash'' + ? has(self.consistentHash) : !has(self.consistentHash)' + - message: Currently SlowStart is only + supported for RoundRobin and LeastRequest + load balancers. + rule: 'self.type in [''Random'', ''ConsistentHash''] + ? !has(self.slowStart) : true ' + proxyProtocol: + description: ProxyProtocol enables the + Proxy Protocol when communicating with + the backend. + properties: + version: + description: |- + Version of ProxyProtol + Valid ProxyProtocolVersion values are + "V1" + "V2" + enum: + - V1 + - V2 + type: string + required: + - version + type: object + retry: + description: |- + Retry provides more advanced usage, allowing users to customize the number of retries, retry fallback strategy, and retry triggering conditions. + If not set, retry will be disabled. + properties: + numRetries: + default: 2 + description: NumRetries is the number + of retries to be attempted. Defaults + to 2. + format: int32 + minimum: 0 + type: integer + perRetry: + description: PerRetry is the retry + policy to be applied per retry attempt. + properties: + backOff: + description: |- + Backoff is the backoff policy to be applied per retry attempt. gateway uses a fully jittered exponential + back-off algorithm for retries. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#config-http-filters-router-x-envoy-max-retries + properties: + baseInterval: + description: BaseInterval + is the base interval between + retries. + format: duration + type: string + maxInterval: + description: |- + MaxInterval is the maximum interval between retries. This parameter is optional, but must be greater than or equal to the base_interval if set. + The default is 10 times the base_interval + format: duration + type: string + type: object + timeout: + description: Timeout is the timeout + per retry attempt. + format: duration + type: string + type: object + retryOn: + description: |- + RetryOn specifies the retry trigger condition. + + If not specified, the default is to retry on connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes(503). + properties: + httpStatusCodes: + description: |- + HttpStatusCodes specifies the http status codes to be retried. + The retriable-status-codes trigger must also be configured for these status codes to trigger a retry. + items: + description: HTTPStatus defines + the http status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + triggers: + description: Triggers specifies + the retry trigger condition(Http/Grpc). + items: + description: TriggerEnum specifies + the conditions that trigger + retries. + enum: + - 5xx + - gateway-error + - reset + - connect-failure + - retriable-4xx + - refused-stream + - retriable-status-codes + - cancelled + - deadline-exceeded + - internal + - resource-exhausted + - unavailable + type: string + type: array + type: object + type: object + tcpKeepalive: + description: |- + TcpKeepalive settings associated with the upstream client connection. + Disabled by default. + properties: + idleTime: + description: |- + The duration a connection needs to be idle before keep-alive + probes start being sent. + The duration format is + Defaults to `7200s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + The duration between keep-alive probes. + Defaults to `75s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + probes: + description: |- + The total number of unacknowledged probes to send before deciding + the connection is dead. + Defaults to 9. + format: int32 + type: integer + type: object + timeout: + description: Timeout settings for the + backend connections. + properties: + http: + description: Timeout settings for + HTTP. + properties: + connectionIdleTimeout: + description: |- + The idle timeout for an HTTP connection. Idle time is defined as a period in which there are no active requests in the connection. + Default: 1 hour. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + maxConnectionDuration: + description: |- + The maximum duration of an HTTP connection. + Default: unlimited. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + requestTimeout: + description: RequestTimeout is + the time until which entire + response is received from the + upstream. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + tcp: + description: Timeout settings for + TCP. + properties: + connectTimeout: + description: |- + The timeout for network connection establishment, including TCP and TLS handshakes. + Default: 10 seconds. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + type: object + type: object + http: + description: HTTP defines additional configuration + specific to HTTP access logs. + properties: + requestHeaders: + description: RequestHeaders defines request + headers to include in log entries sent + to the access log service. + items: + type: string + type: array + responseHeaders: + description: ResponseHeaders defines response + headers to include in log entries sent + to the access log service. + items: + type: string + type: array + responseTrailers: + description: ResponseTrailers defines + response trailers to include in log + entries sent to the access log service. + items: + type: string + type: array + type: object + logName: + description: |- + LogName defines the friendly name of the access log to be returned in + StreamAccessLogsMessage.Identifier. This allows the access log server + to differentiate between different access logs coming from the same Envoy. + minLength: 1 + type: string + type: + description: Type defines the type of accesslog. + Supported types are "HTTP" and "TCP". + enum: + - HTTP + - TCP + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: The http field may only be set when + type is HTTP. + rule: self.type == 'HTTP' || !has(self.http) + - message: BackendRefs must be used, backendRef + is not supported. + rule: '!has(self.backendRef)' + - message: must have at least one backend in backendRefs + rule: has(self.backendRefs) && self.backendRefs.size() + > 0 + - message: BackendRefs only support Service and + Backend kind. + rule: 'has(self.backendRefs) ? self.backendRefs.all(f, + f.kind == ''Service'' || f.kind == ''Backend'') + : true' + - message: BackendRefs only support Core and gateway.envoyproxy.io + group. + rule: 'has(self.backendRefs) ? (self.backendRefs.all(f, + f.group == "" || f.group == ''gateway.envoyproxy.io'')) + : true' + file: + description: File defines the file accesslog sink. + properties: + path: + description: Path defines the file path used + to expose envoy access log(e.g. /dev/stdout). + minLength: 1 + type: string + type: object + openTelemetry: + description: OpenTelemetry defines the OpenTelemetry + accesslog sink. + properties: + backendRef: + description: |- + BackendRef references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + + Deprecated: Use BackendRefs instead. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + backendRefs: + description: |- + BackendRefs references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + items: + description: BackendRef defines how an ObjectReference + that is specific to BackendRef. + properties: + fallback: + description: |- + Fallback indicates whether the backend is designated as a fallback. + Multiple fallback backends can be configured. + It is highly recommended to configure active or passive health checks to ensure that failover can be detected + when the active backends become unhealthy and to automatically readjust once the primary backends are healthy again. + The overprovisioning factor is set to 1.4, meaning the fallback backends will only start receiving traffic when + the health of the active backends falls below 72%. + type: boolean + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the + referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + maxItems: 16 + type: array + backendSettings: + description: |- + BackendSettings holds configuration for managing the connection + to the backend. + properties: + circuitBreaker: + description: |- + Circuit Breaker settings for the upstream connections and requests. + If not set, circuit breakers will be enabled with the default thresholds + properties: + maxConnections: + default: 1024 + description: The maximum number of + connections that Envoy will establish + to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRequests: + default: 1024 + description: The maximum number of + parallel requests that Envoy will + make to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRetries: + default: 1024 + description: The maximum number of + parallel retries that Envoy will + make to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxPendingRequests: + default: 1024 + description: The maximum number of + pending requests that Envoy will + queue to the referenced backend + defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxRequestsPerConnection: + description: |- + The maximum number of requests that Envoy will make over a single connection to the referenced backend defined within a xRoute rule. + Default: unlimited. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + perEndpoint: + description: PerEndpoint defines Circuit + Breakers that will apply per-endpoint + for an upstream cluster + properties: + maxConnections: + default: 1024 + description: MaxConnections configures + the maximum number of connections + that Envoy will establish per-endpoint + to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + type: object + type: object + connection: + description: Connection includes backend + connection settings. + properties: + bufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + BufferLimit Soft limit on size of the cluster’s connections read and write buffers. + BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space. + If unspecified, an implementation defined default is applied (32768 bytes). + For example, 20Mi, 1Gi, 256Ki etc. + Note: that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + socketBufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + SocketBufferLimit provides configuration for the maximum buffer size in bytes for each socket + to backend. + SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space. + For example, 20Mi, 1Gi, 256Ki etc. + Note that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + type: object + dns: + description: DNS includes dns resolution + settings. + properties: + dnsRefreshRate: + description: |- + DNSRefreshRate specifies the rate at which DNS records should be refreshed. + Defaults to 30 seconds. + type: string + lookupFamily: + description: |- + LookupFamily determines how Envoy would resolve DNS for Routes where the backend is specified as a fully qualified domain name (FQDN). + If set, this configuration overrides other defaults. + enum: + - IPv4 + - IPv6 + - IPv4Preferred + - IPv6Preferred + - IPv4AndIPv6 + type: string + respectDnsTtl: + description: |- + RespectDNSTTL indicates whether the DNS Time-To-Live (TTL) should be respected. + If the value is set to true, the DNS refresh rate will be set to the resource record’s TTL. + Defaults to true. + type: boolean + type: object + healthCheck: + description: HealthCheck allows gateway + to perform active health checking on + backends. + properties: + active: + description: Active health check configuration + properties: + grpc: + description: |- + GRPC defines the configuration of the GRPC health checker. + It's optional, and can only be used if the specified type is GRPC. + properties: + service: + description: |- + Service to send in the health check request. + If this is not specified, then the health check request applies to the entire + server and not to a specific service. + type: string + type: object + healthyThreshold: + default: 1 + description: HealthyThreshold + defines the number of healthy + health checks required before + a backend host is marked healthy. + format: int32 + minimum: 1 + type: integer + http: + description: |- + HTTP defines the configuration of http health checker. + It's required while the health checker type is HTTP. + properties: + expectedResponse: + description: ExpectedResponse + defines a list of HTTP expected + responses to match. + properties: + binary: + description: Binary payload + base64 encoded. + format: byte + type: string + text: + description: Text payload + in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines + the type of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type + is Text, text field needs + to be set. + rule: 'self.type == ''Text'' + ? has(self.text) : !has(self.text)' + - message: If payload type + is Binary, binary field + needs to be set. + rule: 'self.type == ''Binary'' + ? has(self.binary) : !has(self.binary)' + expectedStatuses: + description: |- + ExpectedStatuses defines a list of HTTP response statuses considered healthy. + Defaults to 200 only + items: + description: HTTPStatus + defines the http status + code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + method: + description: |- + Method defines the HTTP method used for health checking. + Defaults to GET + type: string + path: + description: Path defines + the HTTP path that will + be requested during health + checking. + maxLength: 1024 + minLength: 1 + type: string + required: + - path + type: object + interval: + default: 3s + description: Interval defines + the time between active health + checks. + format: duration + type: string + tcp: + description: |- + TCP defines the configuration of tcp health checker. + It's required while the health checker type is TCP. + properties: + receive: + description: Receive defines + the expected response payload. + properties: + binary: + description: Binary payload + base64 encoded. + format: byte + type: string + text: + description: Text payload + in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines + the type of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type + is Text, text field needs + to be set. + rule: 'self.type == ''Text'' + ? has(self.text) : !has(self.text)' + - message: If payload type + is Binary, binary field + needs to be set. + rule: 'self.type == ''Binary'' + ? has(self.binary) : !has(self.binary)' + send: + description: Send defines + the request payload. + properties: + binary: + description: Binary payload + base64 encoded. + format: byte + type: string + text: + description: Text payload + in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines + the type of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type + is Text, text field needs + to be set. + rule: 'self.type == ''Text'' + ? has(self.text) : !has(self.text)' + - message: If payload type + is Binary, binary field + needs to be set. + rule: 'self.type == ''Binary'' + ? has(self.binary) : !has(self.binary)' + type: object + timeout: + default: 1s + description: Timeout defines the + time to wait for a health check + response. + format: duration + type: string + type: + allOf: + - enum: + - HTTP + - TCP + - GRPC + - enum: + - HTTP + - TCP + - GRPC + description: Type defines the + type of health checker. + type: string + unhealthyThreshold: + default: 3 + description: UnhealthyThreshold + defines the number of unhealthy + health checks required before + a backend host is marked unhealthy. + format: int32 + minimum: 1 + type: integer + required: + - type + type: object + x-kubernetes-validations: + - message: If Health Checker type + is HTTP, http field needs to be + set. + rule: 'self.type == ''HTTP'' ? has(self.http) + : !has(self.http)' + - message: If Health Checker type + is TCP, tcp field needs to be + set. + rule: 'self.type == ''TCP'' ? has(self.tcp) + : !has(self.tcp)' + - message: The grpc field can only + be set if the Health Checker type + is GRPC. + rule: 'has(self.grpc) ? self.type + == ''GRPC'' : true' + panicThreshold: + description: |- + When number of unhealthy endpoints for a backend reaches this threshold + Envoy will disregard health status and balance across all endpoints. + It's designed to prevent a situation in which host failures cascade throughout the cluster + as load increases. If not set, the default value is 50%. To disable panic mode, set value to `0`. + format: int32 + maximum: 100 + minimum: 0 + type: integer + passive: + description: Passive passive check + configuration + properties: + baseEjectionTime: + default: 30s + description: BaseEjectionTime + defines the base duration for + which a host will be ejected + on consecutive failures. + format: duration + type: string + consecutive5XxErrors: + default: 5 + description: Consecutive5xxErrors + sets the number of consecutive + 5xx errors triggering ejection. + format: int32 + type: integer + consecutiveGatewayErrors: + default: 0 + description: ConsecutiveGatewayErrors + sets the number of consecutive + gateway errors triggering ejection. + format: int32 + type: integer + consecutiveLocalOriginFailures: + default: 5 + description: |- + ConsecutiveLocalOriginFailures sets the number of consecutive local origin failures triggering ejection. + Parameter takes effect only when split_external_local_origin_errors is set to true. + format: int32 + type: integer + interval: + default: 3s + description: Interval defines + the time between passive health + checks. + format: duration + type: string + maxEjectionPercent: + default: 10 + description: MaxEjectionPercent + sets the maximum percentage + of hosts in a cluster that can + be ejected. + format: int32 + type: integer + splitExternalLocalOriginErrors: + default: false + description: SplitExternalLocalOriginErrors + enables splitting of errors + between external and local origin. + type: boolean + type: object + type: object + http2: + description: HTTP2 provides HTTP/2 configuration + for backend connections. + properties: + initialConnectionWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialConnectionWindowSize sets the initial window size for HTTP/2 connections. + If not set, the default value is 1 MiB. + x-kubernetes-int-or-string: true + initialStreamWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialStreamWindowSize sets the initial window size for HTTP/2 streams. + If not set, the default value is 64 KiB(64*1024). + x-kubernetes-int-or-string: true + maxConcurrentStreams: + description: |- + MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection. + If not set, the default value is 100. + format: int32 + maximum: 2147483647 + minimum: 1 + type: integer + onInvalidMessage: + description: |- + OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error + It's recommended for L2 Envoy deployments to set this value to TerminateStream. + https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two + Default: TerminateConnection + type: string + type: object + loadBalancer: + description: |- + LoadBalancer policy to apply when routing traffic from the gateway to + the backend endpoints. Defaults to `LeastRequest`. + properties: + consistentHash: + description: |- + ConsistentHash defines the configuration when the load balancer type is + set to ConsistentHash + properties: + cookie: + description: Cookie configures + the cookie hash policy when + the consistent hash type is + set to Cookie. + properties: + attributes: + additionalProperties: + type: string + description: Additional Attributes + to set for the generated + cookie. + type: object + name: + description: |- + Name of the cookie to hash. + If this cookie does not exist in the request, Envoy will generate a cookie and set + the TTL on the response back to the client based on Layer 4 + attributes of the backend endpoint, to ensure that these future requests + go to the same backend endpoint. Make sure to set the TTL field for this case. + type: string + ttl: + description: |- + TTL of the generated cookie if the cookie is not present. This value sets the + Max-Age attribute value. + type: string + required: + - name + type: object + header: + description: Header configures + the header hash policy when + the consistent hash type is + set to Header. + properties: + name: + description: Name of the header + to hash. + type: string + required: + - name + type: object + tableSize: + default: 65537 + description: The table size for + consistent hashing, must be + prime number limited to 5000011. + format: int64 + maximum: 5000011 + minimum: 2 + type: integer + type: + description: |- + ConsistentHashType defines the type of input to hash on. Valid Type values are + "SourceIP", + "Header", + "Cookie". + enum: + - SourceIP + - Header + - Cookie + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If consistent hash type + is header, the header field must + be set. + rule: 'self.type == ''Header'' ? + has(self.header) : !has(self.header)' + - message: If consistent hash type + is cookie, the cookie field must + be set. + rule: 'self.type == ''Cookie'' ? + has(self.cookie) : !has(self.cookie)' + slowStart: + description: |- + SlowStart defines the configuration related to the slow start load balancer policy. + If set, during slow start window, traffic sent to the newly added hosts will gradually increase. + Currently this is only supported for RoundRobin and LeastRequest load balancers + properties: + window: + description: |- + Window defines the duration of the warm up period for newly added host. + During slow start window, traffic sent to the newly added hosts will gradually increase. + Currently only supports linear growth of traffic. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#config-cluster-v3-cluster-slowstartconfig + type: string + required: + - window + type: object + type: + description: |- + Type decides the type of Load Balancer policy. + Valid LoadBalancerType values are + "ConsistentHash", + "LeastRequest", + "Random", + "RoundRobin". + enum: + - ConsistentHash + - LeastRequest + - Random + - RoundRobin + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If LoadBalancer type is consistentHash, + consistentHash field needs to be set. + rule: 'self.type == ''ConsistentHash'' + ? has(self.consistentHash) : !has(self.consistentHash)' + - message: Currently SlowStart is only + supported for RoundRobin and LeastRequest + load balancers. + rule: 'self.type in [''Random'', ''ConsistentHash''] + ? !has(self.slowStart) : true ' + proxyProtocol: + description: ProxyProtocol enables the + Proxy Protocol when communicating with + the backend. + properties: + version: + description: |- + Version of ProxyProtol + Valid ProxyProtocolVersion values are + "V1" + "V2" + enum: + - V1 + - V2 + type: string + required: + - version + type: object + retry: + description: |- + Retry provides more advanced usage, allowing users to customize the number of retries, retry fallback strategy, and retry triggering conditions. + If not set, retry will be disabled. + properties: + numRetries: + default: 2 + description: NumRetries is the number + of retries to be attempted. Defaults + to 2. + format: int32 + minimum: 0 + type: integer + perRetry: + description: PerRetry is the retry + policy to be applied per retry attempt. + properties: + backOff: + description: |- + Backoff is the backoff policy to be applied per retry attempt. gateway uses a fully jittered exponential + back-off algorithm for retries. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#config-http-filters-router-x-envoy-max-retries + properties: + baseInterval: + description: BaseInterval + is the base interval between + retries. + format: duration + type: string + maxInterval: + description: |- + MaxInterval is the maximum interval between retries. This parameter is optional, but must be greater than or equal to the base_interval if set. + The default is 10 times the base_interval + format: duration + type: string + type: object + timeout: + description: Timeout is the timeout + per retry attempt. + format: duration + type: string + type: object + retryOn: + description: |- + RetryOn specifies the retry trigger condition. + + If not specified, the default is to retry on connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes(503). + properties: + httpStatusCodes: + description: |- + HttpStatusCodes specifies the http status codes to be retried. + The retriable-status-codes trigger must also be configured for these status codes to trigger a retry. + items: + description: HTTPStatus defines + the http status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + triggers: + description: Triggers specifies + the retry trigger condition(Http/Grpc). + items: + description: TriggerEnum specifies + the conditions that trigger + retries. + enum: + - 5xx + - gateway-error + - reset + - connect-failure + - retriable-4xx + - refused-stream + - retriable-status-codes + - cancelled + - deadline-exceeded + - internal + - resource-exhausted + - unavailable + type: string + type: array + type: object + type: object + tcpKeepalive: + description: |- + TcpKeepalive settings associated with the upstream client connection. + Disabled by default. + properties: + idleTime: + description: |- + The duration a connection needs to be idle before keep-alive + probes start being sent. + The duration format is + Defaults to `7200s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + The duration between keep-alive probes. + Defaults to `75s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + probes: + description: |- + The total number of unacknowledged probes to send before deciding + the connection is dead. + Defaults to 9. + format: int32 + type: integer + type: object + timeout: + description: Timeout settings for the + backend connections. + properties: + http: + description: Timeout settings for + HTTP. + properties: + connectionIdleTimeout: + description: |- + The idle timeout for an HTTP connection. Idle time is defined as a period in which there are no active requests in the connection. + Default: 1 hour. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + maxConnectionDuration: + description: |- + The maximum duration of an HTTP connection. + Default: unlimited. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + requestTimeout: + description: RequestTimeout is + the time until which entire + response is received from the + upstream. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + tcp: + description: Timeout settings for + TCP. + properties: + connectTimeout: + description: |- + The timeout for network connection establishment, including TCP and TLS handshakes. + Default: 10 seconds. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + type: object + type: object + host: + description: |- + Host define the extension service hostname. + Deprecated: Use BackendRefs instead. + type: string + port: + default: 4317 + description: |- + Port defines the port the extension service is exposed on. + Deprecated: Use BackendRefs instead. + format: int32 + minimum: 0 + type: integer + resources: + additionalProperties: + type: string + description: |- + Resources is a set of labels that describe the source of a log entry, including envoy node info. + It's recommended to follow [semantic conventions](https://opentelemetry.io/docs/reference/specification/resource/semantic_conventions/). + type: object + type: object + x-kubernetes-validations: + - message: host or backendRefs needs to be set + rule: has(self.host) || self.backendRefs.size() + > 0 + - message: BackendRefs must be used, backendRef + is not supported. + rule: '!has(self.backendRef)' + - message: BackendRefs only support Service and + Backend kind. + rule: 'has(self.backendRefs) ? self.backendRefs.all(f, + f.kind == ''Service'' || f.kind == ''Backend'') + : true' + - message: BackendRefs only support Core and gateway.envoyproxy.io + group. + rule: 'has(self.backendRefs) ? (self.backendRefs.all(f, + f.group == "" || f.group == ''gateway.envoyproxy.io'')) + : true' + type: + description: Type defines the type of accesslog + sink. + enum: + - ALS + - File + - OpenTelemetry + type: string + type: object + x-kubernetes-validations: + - message: If AccessLogSink type is ALS, als field + needs to be set. + rule: 'self.type == ''ALS'' ? has(self.als) : !has(self.als)' + - message: If AccessLogSink type is File, file field + needs to be set. + rule: 'self.type == ''File'' ? has(self.file) : + !has(self.file)' + - message: If AccessLogSink type is OpenTelemetry, + openTelemetry field needs to be set. + rule: 'self.type == ''OpenTelemetry'' ? has(self.openTelemetry) + : !has(self.openTelemetry)' + maxItems: 50 + minItems: 1 + type: array + type: + description: |- + Type defines the component emitting the accesslog, such as Listener and Route. + If type not defined, the setting would apply to: + (1) All Routes. + (2) Listeners if and only if Envoy does not find a matching route for a request. + If type is defined, the accesslog settings would apply to the relevant component (as-is). + enum: + - Listener + - Route + type: string + required: + - sinks + type: object + maxItems: 50 + minItems: 1 + type: array + type: object + metrics: + description: Metrics defines metrics configuration for managed + proxies. + properties: + enablePerEndpointStats: + description: |- + EnablePerEndpointStats enables per endpoint envoy stats metrics. + Please use with caution. + type: boolean + enableRequestResponseSizesStats: + description: EnableRequestResponseSizesStats enables publishing + of histograms tracking header and body sizes of requests + and responses. + type: boolean + enableVirtualHostStats: + description: EnableVirtualHostStats enables envoy stat metrics + for virtual hosts. + type: boolean + matches: + description: |- + Matches defines configuration for selecting specific metrics instead of generating all metrics stats + that are enabled by default. This helps reduce CPU and memory overhead in Envoy, but eliminating some stats + may after critical functionality. Here are the stats that we strongly recommend not disabling: + `cluster_manager.warming_clusters`, `cluster..membership_total`,`cluster..membership_healthy`, + `cluster..membership_degraded`,reference https://github.com/envoyproxy/envoy/issues/9856, + https://github.com/envoyproxy/envoy/issues/14610 + items: + description: |- + StringMatch defines how to match any strings. + This is a general purpose match condition that can be used by other EG APIs + that need to match against a string. + properties: + type: + default: Exact + description: Type specifies how to match against a string. + enum: + - Exact + - Prefix + - Suffix + - RegularExpression + type: string + value: + description: Value specifies the string value that the + match must have. + maxLength: 1024 + minLength: 1 + type: string + required: + - value + type: object + type: array + prometheus: + description: Prometheus defines the configuration for Admin + endpoint `/stats/prometheus`. + properties: + compression: + description: Configure the compression on Prometheus endpoint. + Compression is useful in situations when bandwidth is + scarce and large payloads can be effectively compressed + at the expense of higher CPU load. + properties: + brotli: + description: The configuration for Brotli compressor. + type: object + gzip: + description: The configuration for GZIP compressor. + type: object + type: + description: CompressorType defines the compressor + type to use for compression. + enum: + - Gzip + - Brotli + type: string + required: + - type + type: object + disable: + description: Disable the Prometheus endpoint. + type: boolean + type: object + sinks: + description: Sinks defines the metric sinks where metrics + are sent to. + items: + description: |- + ProxyMetricSink defines the sink of metrics. + Default metrics sink is OpenTelemetry. + properties: + openTelemetry: + description: |- + OpenTelemetry defines the configuration for OpenTelemetry sink. + It's required if the sink type is OpenTelemetry. + properties: + backendRef: + description: |- + BackendRef references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + + Deprecated: Use BackendRefs instead. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == + ''Service'') ? has(self.port) : true' + backendRefs: + description: |- + BackendRefs references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + items: + description: BackendRef defines how an ObjectReference + that is specific to BackendRef. + properties: + fallback: + description: |- + Fallback indicates whether the backend is designated as a fallback. + Multiple fallback backends can be configured. + It is highly recommended to configure active or passive health checks to ensure that failover can be detected + when the active backends become unhealthy and to automatically readjust once the primary backends are healthy again. + The overprovisioning factor is set to 1.4, meaning the fallback backends will only start receiving traffic when + the health of the active backends falls below 72%. + type: boolean + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == + ''Service'') ? has(self.port) : true' + maxItems: 16 + type: array + backendSettings: + description: |- + BackendSettings holds configuration for managing the connection + to the backend. + properties: + circuitBreaker: + description: |- + Circuit Breaker settings for the upstream connections and requests. + If not set, circuit breakers will be enabled with the default thresholds + properties: + maxConnections: + default: 1024 + description: The maximum number of connections + that Envoy will establish to the referenced + backend defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRequests: + default: 1024 + description: The maximum number of parallel + requests that Envoy will make to the referenced + backend defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRetries: + default: 1024 + description: The maximum number of parallel + retries that Envoy will make to the referenced + backend defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxPendingRequests: + default: 1024 + description: The maximum number of pending + requests that Envoy will queue to the + referenced backend defined within a xRoute + rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxRequestsPerConnection: + description: |- + The maximum number of requests that Envoy will make over a single connection to the referenced backend defined within a xRoute rule. + Default: unlimited. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + perEndpoint: + description: PerEndpoint defines Circuit + Breakers that will apply per-endpoint + for an upstream cluster + properties: + maxConnections: + default: 1024 + description: MaxConnections configures + the maximum number of connections + that Envoy will establish per-endpoint + to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + type: object + type: object + connection: + description: Connection includes backend connection + settings. + properties: + bufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + BufferLimit Soft limit on size of the cluster’s connections read and write buffers. + BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space. + If unspecified, an implementation defined default is applied (32768 bytes). + For example, 20Mi, 1Gi, 256Ki etc. + Note: that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + socketBufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + SocketBufferLimit provides configuration for the maximum buffer size in bytes for each socket + to backend. + SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space. + For example, 20Mi, 1Gi, 256Ki etc. + Note that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + type: object + dns: + description: DNS includes dns resolution settings. + properties: + dnsRefreshRate: + description: |- + DNSRefreshRate specifies the rate at which DNS records should be refreshed. + Defaults to 30 seconds. + type: string + lookupFamily: + description: |- + LookupFamily determines how Envoy would resolve DNS for Routes where the backend is specified as a fully qualified domain name (FQDN). + If set, this configuration overrides other defaults. + enum: + - IPv4 + - IPv6 + - IPv4Preferred + - IPv6Preferred + - IPv4AndIPv6 + type: string + respectDnsTtl: + description: |- + RespectDNSTTL indicates whether the DNS Time-To-Live (TTL) should be respected. + If the value is set to true, the DNS refresh rate will be set to the resource record’s TTL. + Defaults to true. + type: boolean + type: object + healthCheck: + description: HealthCheck allows gateway to perform + active health checking on backends. + properties: + active: + description: Active health check configuration + properties: + grpc: + description: |- + GRPC defines the configuration of the GRPC health checker. + It's optional, and can only be used if the specified type is GRPC. + properties: + service: + description: |- + Service to send in the health check request. + If this is not specified, then the health check request applies to the entire + server and not to a specific service. + type: string + type: object + healthyThreshold: + default: 1 + description: HealthyThreshold defines + the number of healthy health checks + required before a backend host is + marked healthy. + format: int32 + minimum: 1 + type: integer + http: + description: |- + HTTP defines the configuration of http health checker. + It's required while the health checker type is HTTP. + properties: + expectedResponse: + description: ExpectedResponse defines + a list of HTTP expected responses + to match. + properties: + binary: + description: Binary payload + base64 encoded. + format: byte + type: string + text: + description: Text payload in + plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the + type of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, + text field needs to be set. + rule: 'self.type == ''Text'' ? + has(self.text) : !has(self.text)' + - message: If payload type is Binary, + binary field needs to be set. + rule: 'self.type == ''Binary'' + ? has(self.binary) : !has(self.binary)' + expectedStatuses: + description: |- + ExpectedStatuses defines a list of HTTP response statuses considered healthy. + Defaults to 200 only + items: + description: HTTPStatus defines + the http status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + method: + description: |- + Method defines the HTTP method used for health checking. + Defaults to GET + type: string + path: + description: Path defines the HTTP + path that will be requested during + health checking. + maxLength: 1024 + minLength: 1 + type: string + required: + - path + type: object + interval: + default: 3s + description: Interval defines the time + between active health checks. + format: duration + type: string + tcp: + description: |- + TCP defines the configuration of tcp health checker. + It's required while the health checker type is TCP. + properties: + receive: + description: Receive defines the + expected response payload. + properties: + binary: + description: Binary payload + base64 encoded. + format: byte + type: string + text: + description: Text payload in + plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the + type of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, + text field needs to be set. + rule: 'self.type == ''Text'' ? + has(self.text) : !has(self.text)' + - message: If payload type is Binary, + binary field needs to be set. + rule: 'self.type == ''Binary'' + ? has(self.binary) : !has(self.binary)' + send: + description: Send defines the request + payload. + properties: + binary: + description: Binary payload + base64 encoded. + format: byte + type: string + text: + description: Text payload in + plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the + type of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, + text field needs to be set. + rule: 'self.type == ''Text'' ? + has(self.text) : !has(self.text)' + - message: If payload type is Binary, + binary field needs to be set. + rule: 'self.type == ''Binary'' + ? has(self.binary) : !has(self.binary)' + type: object + timeout: + default: 1s + description: Timeout defines the time + to wait for a health check response. + format: duration + type: string + type: + allOf: + - enum: + - HTTP + - TCP + - GRPC + - enum: + - HTTP + - TCP + - GRPC + description: Type defines the type of + health checker. + type: string + unhealthyThreshold: + default: 3 + description: UnhealthyThreshold defines + the number of unhealthy health checks + required before a backend host is + marked unhealthy. + format: int32 + minimum: 1 + type: integer + required: + - type + type: object + x-kubernetes-validations: + - message: If Health Checker type is HTTP, + http field needs to be set. + rule: 'self.type == ''HTTP'' ? has(self.http) + : !has(self.http)' + - message: If Health Checker type is TCP, + tcp field needs to be set. + rule: 'self.type == ''TCP'' ? has(self.tcp) + : !has(self.tcp)' + - message: The grpc field can only be set + if the Health Checker type is GRPC. + rule: 'has(self.grpc) ? self.type == ''GRPC'' + : true' + panicThreshold: + description: |- + When number of unhealthy endpoints for a backend reaches this threshold + Envoy will disregard health status and balance across all endpoints. + It's designed to prevent a situation in which host failures cascade throughout the cluster + as load increases. If not set, the default value is 50%. To disable panic mode, set value to `0`. + format: int32 + maximum: 100 + minimum: 0 + type: integer + passive: + description: Passive passive check configuration + properties: + baseEjectionTime: + default: 30s + description: BaseEjectionTime defines + the base duration for which a host + will be ejected on consecutive failures. + format: duration + type: string + consecutive5XxErrors: + default: 5 + description: Consecutive5xxErrors sets + the number of consecutive 5xx errors + triggering ejection. + format: int32 + type: integer + consecutiveGatewayErrors: + default: 0 + description: ConsecutiveGatewayErrors + sets the number of consecutive gateway + errors triggering ejection. + format: int32 + type: integer + consecutiveLocalOriginFailures: + default: 5 + description: |- + ConsecutiveLocalOriginFailures sets the number of consecutive local origin failures triggering ejection. + Parameter takes effect only when split_external_local_origin_errors is set to true. + format: int32 + type: integer + interval: + default: 3s + description: Interval defines the time + between passive health checks. + format: duration + type: string + maxEjectionPercent: + default: 10 + description: MaxEjectionPercent sets + the maximum percentage of hosts in + a cluster that can be ejected. + format: int32 + type: integer + splitExternalLocalOriginErrors: + default: false + description: SplitExternalLocalOriginErrors + enables splitting of errors between + external and local origin. + type: boolean + type: object + type: object + http2: + description: HTTP2 provides HTTP/2 configuration + for backend connections. + properties: + initialConnectionWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialConnectionWindowSize sets the initial window size for HTTP/2 connections. + If not set, the default value is 1 MiB. + x-kubernetes-int-or-string: true + initialStreamWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialStreamWindowSize sets the initial window size for HTTP/2 streams. + If not set, the default value is 64 KiB(64*1024). + x-kubernetes-int-or-string: true + maxConcurrentStreams: + description: |- + MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection. + If not set, the default value is 100. + format: int32 + maximum: 2147483647 + minimum: 1 + type: integer + onInvalidMessage: + description: |- + OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error + It's recommended for L2 Envoy deployments to set this value to TerminateStream. + https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two + Default: TerminateConnection + type: string + type: object + loadBalancer: + description: |- + LoadBalancer policy to apply when routing traffic from the gateway to + the backend endpoints. Defaults to `LeastRequest`. + properties: + consistentHash: + description: |- + ConsistentHash defines the configuration when the load balancer type is + set to ConsistentHash + properties: + cookie: + description: Cookie configures the cookie + hash policy when the consistent hash + type is set to Cookie. + properties: + attributes: + additionalProperties: + type: string + description: Additional Attributes + to set for the generated cookie. + type: object + name: + description: |- + Name of the cookie to hash. + If this cookie does not exist in the request, Envoy will generate a cookie and set + the TTL on the response back to the client based on Layer 4 + attributes of the backend endpoint, to ensure that these future requests + go to the same backend endpoint. Make sure to set the TTL field for this case. + type: string + ttl: + description: |- + TTL of the generated cookie if the cookie is not present. This value sets the + Max-Age attribute value. + type: string + required: + - name + type: object + header: + description: Header configures the header + hash policy when the consistent hash + type is set to Header. + properties: + name: + description: Name of the header + to hash. + type: string + required: + - name + type: object + tableSize: + default: 65537 + description: The table size for consistent + hashing, must be prime number limited + to 5000011. + format: int64 + maximum: 5000011 + minimum: 2 + type: integer + type: + description: |- + ConsistentHashType defines the type of input to hash on. Valid Type values are + "SourceIP", + "Header", + "Cookie". + enum: + - SourceIP + - Header + - Cookie + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If consistent hash type is header, + the header field must be set. + rule: 'self.type == ''Header'' ? has(self.header) + : !has(self.header)' + - message: If consistent hash type is cookie, + the cookie field must be set. + rule: 'self.type == ''Cookie'' ? has(self.cookie) + : !has(self.cookie)' + slowStart: + description: |- + SlowStart defines the configuration related to the slow start load balancer policy. + If set, during slow start window, traffic sent to the newly added hosts will gradually increase. + Currently this is only supported for RoundRobin and LeastRequest load balancers + properties: + window: + description: |- + Window defines the duration of the warm up period for newly added host. + During slow start window, traffic sent to the newly added hosts will gradually increase. + Currently only supports linear growth of traffic. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#config-cluster-v3-cluster-slowstartconfig + type: string + required: + - window + type: object + type: + description: |- + Type decides the type of Load Balancer policy. + Valid LoadBalancerType values are + "ConsistentHash", + "LeastRequest", + "Random", + "RoundRobin". + enum: + - ConsistentHash + - LeastRequest + - Random + - RoundRobin + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If LoadBalancer type is consistentHash, + consistentHash field needs to be set. + rule: 'self.type == ''ConsistentHash'' ? has(self.consistentHash) + : !has(self.consistentHash)' + - message: Currently SlowStart is only supported + for RoundRobin and LeastRequest load balancers. + rule: 'self.type in [''Random'', ''ConsistentHash''] + ? !has(self.slowStart) : true ' + proxyProtocol: + description: ProxyProtocol enables the Proxy + Protocol when communicating with the backend. + properties: + version: + description: |- + Version of ProxyProtol + Valid ProxyProtocolVersion values are + "V1" + "V2" + enum: + - V1 + - V2 + type: string + required: + - version + type: object + retry: + description: |- + Retry provides more advanced usage, allowing users to customize the number of retries, retry fallback strategy, and retry triggering conditions. + If not set, retry will be disabled. + properties: + numRetries: + default: 2 + description: NumRetries is the number of + retries to be attempted. Defaults to 2. + format: int32 + minimum: 0 + type: integer + perRetry: + description: PerRetry is the retry policy + to be applied per retry attempt. + properties: + backOff: + description: |- + Backoff is the backoff policy to be applied per retry attempt. gateway uses a fully jittered exponential + back-off algorithm for retries. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#config-http-filters-router-x-envoy-max-retries + properties: + baseInterval: + description: BaseInterval is the + base interval between retries. + format: duration + type: string + maxInterval: + description: |- + MaxInterval is the maximum interval between retries. This parameter is optional, but must be greater than or equal to the base_interval if set. + The default is 10 times the base_interval + format: duration + type: string + type: object + timeout: + description: Timeout is the timeout + per retry attempt. + format: duration + type: string + type: object + retryOn: + description: |- + RetryOn specifies the retry trigger condition. + + If not specified, the default is to retry on connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes(503). + properties: + httpStatusCodes: + description: |- + HttpStatusCodes specifies the http status codes to be retried. + The retriable-status-codes trigger must also be configured for these status codes to trigger a retry. + items: + description: HTTPStatus defines the + http status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + triggers: + description: Triggers specifies the + retry trigger condition(Http/Grpc). + items: + description: TriggerEnum specifies + the conditions that trigger retries. + enum: + - 5xx + - gateway-error + - reset + - connect-failure + - retriable-4xx + - refused-stream + - retriable-status-codes + - cancelled + - deadline-exceeded + - internal + - resource-exhausted + - unavailable + type: string + type: array + type: object + type: object + tcpKeepalive: + description: |- + TcpKeepalive settings associated with the upstream client connection. + Disabled by default. + properties: + idleTime: + description: |- + The duration a connection needs to be idle before keep-alive + probes start being sent. + The duration format is + Defaults to `7200s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + The duration between keep-alive probes. + Defaults to `75s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + probes: + description: |- + The total number of unacknowledged probes to send before deciding + the connection is dead. + Defaults to 9. + format: int32 + type: integer + type: object + timeout: + description: Timeout settings for the backend + connections. + properties: + http: + description: Timeout settings for HTTP. + properties: + connectionIdleTimeout: + description: |- + The idle timeout for an HTTP connection. Idle time is defined as a period in which there are no active requests in the connection. + Default: 1 hour. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + maxConnectionDuration: + description: |- + The maximum duration of an HTTP connection. + Default: unlimited. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + requestTimeout: + description: RequestTimeout is the time + until which entire response is received + from the upstream. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + tcp: + description: Timeout settings for TCP. + properties: + connectTimeout: + description: |- + The timeout for network connection establishment, including TCP and TLS handshakes. + Default: 10 seconds. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + type: object + type: object + host: + description: |- + Host define the service hostname. + Deprecated: Use BackendRefs instead. + type: string + port: + default: 4317 + description: |- + Port defines the port the service is exposed on. + Deprecated: Use BackendRefs instead. + format: int32 + maximum: 65535 + minimum: 0 + type: integer + type: object + x-kubernetes-validations: + - message: host or backendRefs needs to be set + rule: has(self.host) || self.backendRefs.size() > + 0 + - message: BackendRefs must be used, backendRef is not + supported. + rule: '!has(self.backendRef)' + - message: BackendRefs only support Service and Backend + kind. + rule: 'has(self.backendRefs) ? self.backendRefs.all(f, + f.kind == ''Service'' || f.kind == ''Backend'') + : true' + - message: BackendRefs only support Core and gateway.envoyproxy.io + group. + rule: 'has(self.backendRefs) ? (self.backendRefs.all(f, + f.group == "" || f.group == ''gateway.envoyproxy.io'')) + : true' + type: + default: OpenTelemetry + description: |- + Type defines the metric sink type. + EG currently only supports OpenTelemetry. + enum: + - OpenTelemetry + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If MetricSink type is OpenTelemetry, openTelemetry + field needs to be set. + rule: 'self.type == ''OpenTelemetry'' ? has(self.openTelemetry) + : !has(self.openTelemetry)' + maxItems: 16 + type: array + type: object + tracing: + description: |- + Tracing defines tracing configuration for managed proxies. + If unspecified, will not send tracing data. + properties: + customTags: + additionalProperties: + properties: + environment: + description: |- + Environment adds value from environment variable to each span. + It's required when the type is "Environment". + properties: + defaultValue: + description: DefaultValue defines the default value + to use if the environment variable is not set. + type: string + name: + description: Name defines the name of the environment + variable which to extract the value from. + type: string + required: + - name + type: object + literal: + description: |- + Literal adds hard-coded value to each span. + It's required when the type is "Literal". + properties: + value: + description: Value defines the hard-coded value + to add to each span. + type: string + required: + - value + type: object + requestHeader: + description: |- + RequestHeader adds value from request header to each span. + It's required when the type is "RequestHeader". + properties: + defaultValue: + description: DefaultValue defines the default value + to use if the request header is not set. + type: string + name: + description: Name defines the name of the request + header which to extract the value from. + type: string + required: + - name + type: object + type: + default: Literal + description: Type defines the type of custom tag. + enum: + - Literal + - Environment + - RequestHeader + type: string + required: + - type + type: object + description: |- + CustomTags defines the custom tags to add to each span. + If provider is kubernetes, pod name and namespace are added by default. + type: object + provider: + description: Provider defines the tracing provider. + properties: + backendRef: + description: |- + BackendRef references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + + Deprecated: Use BackendRefs instead. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + backendRefs: + description: |- + BackendRefs references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + items: + description: BackendRef defines how an ObjectReference + that is specific to BackendRef. + properties: + fallback: + description: |- + Fallback indicates whether the backend is designated as a fallback. + Multiple fallback backends can be configured. + It is highly recommended to configure active or passive health checks to ensure that failover can be detected + when the active backends become unhealthy and to automatically readjust once the primary backends are healthy again. + The overprovisioning factor is set to 1.4, meaning the fallback backends will only start receiving traffic when + the health of the active backends falls below 72%. + type: boolean + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + backendSettings: + description: |- + BackendSettings holds configuration for managing the connection + to the backend. + properties: + circuitBreaker: + description: |- + Circuit Breaker settings for the upstream connections and requests. + If not set, circuit breakers will be enabled with the default thresholds + properties: + maxConnections: + default: 1024 + description: The maximum number of connections + that Envoy will establish to the referenced + backend defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRequests: + default: 1024 + description: The maximum number of parallel requests + that Envoy will make to the referenced backend + defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRetries: + default: 1024 + description: The maximum number of parallel retries + that Envoy will make to the referenced backend + defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxPendingRequests: + default: 1024 + description: The maximum number of pending requests + that Envoy will queue to the referenced backend + defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxRequestsPerConnection: + description: |- + The maximum number of requests that Envoy will make over a single connection to the referenced backend defined within a xRoute rule. + Default: unlimited. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + perEndpoint: + description: PerEndpoint defines Circuit Breakers + that will apply per-endpoint for an upstream + cluster + properties: + maxConnections: + default: 1024 + description: MaxConnections configures the + maximum number of connections that Envoy + will establish per-endpoint to the referenced + backend defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + type: object + type: object + connection: + description: Connection includes backend connection + settings. + properties: + bufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + BufferLimit Soft limit on size of the cluster’s connections read and write buffers. + BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space. + If unspecified, an implementation defined default is applied (32768 bytes). + For example, 20Mi, 1Gi, 256Ki etc. + Note: that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + socketBufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + SocketBufferLimit provides configuration for the maximum buffer size in bytes for each socket + to backend. + SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space. + For example, 20Mi, 1Gi, 256Ki etc. + Note that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + type: object + dns: + description: DNS includes dns resolution settings. + properties: + dnsRefreshRate: + description: |- + DNSRefreshRate specifies the rate at which DNS records should be refreshed. + Defaults to 30 seconds. + type: string + lookupFamily: + description: |- + LookupFamily determines how Envoy would resolve DNS for Routes where the backend is specified as a fully qualified domain name (FQDN). + If set, this configuration overrides other defaults. + enum: + - IPv4 + - IPv6 + - IPv4Preferred + - IPv6Preferred + - IPv4AndIPv6 + type: string + respectDnsTtl: + description: |- + RespectDNSTTL indicates whether the DNS Time-To-Live (TTL) should be respected. + If the value is set to true, the DNS refresh rate will be set to the resource record’s TTL. + Defaults to true. + type: boolean + type: object + healthCheck: + description: HealthCheck allows gateway to perform + active health checking on backends. + properties: + active: + description: Active health check configuration + properties: + grpc: + description: |- + GRPC defines the configuration of the GRPC health checker. + It's optional, and can only be used if the specified type is GRPC. + properties: + service: + description: |- + Service to send in the health check request. + If this is not specified, then the health check request applies to the entire + server and not to a specific service. + type: string + type: object + healthyThreshold: + default: 1 + description: HealthyThreshold defines the + number of healthy health checks required + before a backend host is marked healthy. + format: int32 + minimum: 1 + type: integer + http: + description: |- + HTTP defines the configuration of http health checker. + It's required while the health checker type is HTTP. + properties: + expectedResponse: + description: ExpectedResponse defines + a list of HTTP expected responses to + match. + properties: + binary: + description: Binary payload base64 + encoded. + format: byte + type: string + text: + description: Text payload in plain + text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type + of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text + field needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, + binary field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + expectedStatuses: + description: |- + ExpectedStatuses defines a list of HTTP response statuses considered healthy. + Defaults to 200 only + items: + description: HTTPStatus defines the + http status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + method: + description: |- + Method defines the HTTP method used for health checking. + Defaults to GET + type: string + path: + description: Path defines the HTTP path + that will be requested during health + checking. + maxLength: 1024 + minLength: 1 + type: string + required: + - path + type: object + interval: + default: 3s + description: Interval defines the time between + active health checks. + format: duration + type: string + tcp: + description: |- + TCP defines the configuration of tcp health checker. + It's required while the health checker type is TCP. + properties: + receive: + description: Receive defines the expected + response payload. + properties: + binary: + description: Binary payload base64 + encoded. + format: byte + type: string + text: + description: Text payload in plain + text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type + of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text + field needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, + binary field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + send: + description: Send defines the request + payload. + properties: + binary: + description: Binary payload base64 + encoded. + format: byte + type: string + text: + description: Text payload in plain + text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type + of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text + field needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, + binary field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + type: object + timeout: + default: 1s + description: Timeout defines the time to wait + for a health check response. + format: duration + type: string + type: + allOf: + - enum: + - HTTP + - TCP + - GRPC + - enum: + - HTTP + - TCP + - GRPC + description: Type defines the type of health + checker. + type: string + unhealthyThreshold: + default: 3 + description: UnhealthyThreshold defines the + number of unhealthy health checks required + before a backend host is marked unhealthy. + format: int32 + minimum: 1 + type: integer + required: + - type + type: object + x-kubernetes-validations: + - message: If Health Checker type is HTTP, http + field needs to be set. + rule: 'self.type == ''HTTP'' ? has(self.http) + : !has(self.http)' + - message: If Health Checker type is TCP, tcp + field needs to be set. + rule: 'self.type == ''TCP'' ? has(self.tcp) + : !has(self.tcp)' + - message: The grpc field can only be set if the + Health Checker type is GRPC. + rule: 'has(self.grpc) ? self.type == ''GRPC'' + : true' + panicThreshold: + description: |- + When number of unhealthy endpoints for a backend reaches this threshold + Envoy will disregard health status and balance across all endpoints. + It's designed to prevent a situation in which host failures cascade throughout the cluster + as load increases. If not set, the default value is 50%. To disable panic mode, set value to `0`. + format: int32 + maximum: 100 + minimum: 0 + type: integer + passive: + description: Passive passive check configuration + properties: + baseEjectionTime: + default: 30s + description: BaseEjectionTime defines the + base duration for which a host will be ejected + on consecutive failures. + format: duration + type: string + consecutive5XxErrors: + default: 5 + description: Consecutive5xxErrors sets the + number of consecutive 5xx errors triggering + ejection. + format: int32 + type: integer + consecutiveGatewayErrors: + default: 0 + description: ConsecutiveGatewayErrors sets + the number of consecutive gateway errors + triggering ejection. + format: int32 + type: integer + consecutiveLocalOriginFailures: + default: 5 + description: |- + ConsecutiveLocalOriginFailures sets the number of consecutive local origin failures triggering ejection. + Parameter takes effect only when split_external_local_origin_errors is set to true. + format: int32 + type: integer + interval: + default: 3s + description: Interval defines the time between + passive health checks. + format: duration + type: string + maxEjectionPercent: + default: 10 + description: MaxEjectionPercent sets the maximum + percentage of hosts in a cluster that can + be ejected. + format: int32 + type: integer + splitExternalLocalOriginErrors: + default: false + description: SplitExternalLocalOriginErrors + enables splitting of errors between external + and local origin. + type: boolean + type: object + type: object + http2: + description: HTTP2 provides HTTP/2 configuration for + backend connections. + properties: + initialConnectionWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialConnectionWindowSize sets the initial window size for HTTP/2 connections. + If not set, the default value is 1 MiB. + x-kubernetes-int-or-string: true + initialStreamWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialStreamWindowSize sets the initial window size for HTTP/2 streams. + If not set, the default value is 64 KiB(64*1024). + x-kubernetes-int-or-string: true + maxConcurrentStreams: + description: |- + MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection. + If not set, the default value is 100. + format: int32 + maximum: 2147483647 + minimum: 1 + type: integer + onInvalidMessage: + description: |- + OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error + It's recommended for L2 Envoy deployments to set this value to TerminateStream. + https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two + Default: TerminateConnection + type: string + type: object + loadBalancer: + description: |- + LoadBalancer policy to apply when routing traffic from the gateway to + the backend endpoints. Defaults to `LeastRequest`. + properties: + consistentHash: + description: |- + ConsistentHash defines the configuration when the load balancer type is + set to ConsistentHash + properties: + cookie: + description: Cookie configures the cookie + hash policy when the consistent hash type + is set to Cookie. + properties: + attributes: + additionalProperties: + type: string + description: Additional Attributes to + set for the generated cookie. + type: object + name: + description: |- + Name of the cookie to hash. + If this cookie does not exist in the request, Envoy will generate a cookie and set + the TTL on the response back to the client based on Layer 4 + attributes of the backend endpoint, to ensure that these future requests + go to the same backend endpoint. Make sure to set the TTL field for this case. + type: string + ttl: + description: |- + TTL of the generated cookie if the cookie is not present. This value sets the + Max-Age attribute value. + type: string + required: + - name + type: object + header: + description: Header configures the header + hash policy when the consistent hash type + is set to Header. + properties: + name: + description: Name of the header to hash. + type: string + required: + - name + type: object + tableSize: + default: 65537 + description: The table size for consistent + hashing, must be prime number limited to + 5000011. + format: int64 + maximum: 5000011 + minimum: 2 + type: integer + type: + description: |- + ConsistentHashType defines the type of input to hash on. Valid Type values are + "SourceIP", + "Header", + "Cookie". + enum: + - SourceIP + - Header + - Cookie + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If consistent hash type is header, + the header field must be set. + rule: 'self.type == ''Header'' ? has(self.header) + : !has(self.header)' + - message: If consistent hash type is cookie, + the cookie field must be set. + rule: 'self.type == ''Cookie'' ? has(self.cookie) + : !has(self.cookie)' + slowStart: + description: |- + SlowStart defines the configuration related to the slow start load balancer policy. + If set, during slow start window, traffic sent to the newly added hosts will gradually increase. + Currently this is only supported for RoundRobin and LeastRequest load balancers + properties: + window: + description: |- + Window defines the duration of the warm up period for newly added host. + During slow start window, traffic sent to the newly added hosts will gradually increase. + Currently only supports linear growth of traffic. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#config-cluster-v3-cluster-slowstartconfig + type: string + required: + - window + type: object + type: + description: |- + Type decides the type of Load Balancer policy. + Valid LoadBalancerType values are + "ConsistentHash", + "LeastRequest", + "Random", + "RoundRobin". + enum: + - ConsistentHash + - LeastRequest + - Random + - RoundRobin + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If LoadBalancer type is consistentHash, + consistentHash field needs to be set. + rule: 'self.type == ''ConsistentHash'' ? has(self.consistentHash) + : !has(self.consistentHash)' + - message: Currently SlowStart is only supported for + RoundRobin and LeastRequest load balancers. + rule: 'self.type in [''Random'', ''ConsistentHash''] + ? !has(self.slowStart) : true ' + proxyProtocol: + description: ProxyProtocol enables the Proxy Protocol + when communicating with the backend. + properties: + version: + description: |- + Version of ProxyProtol + Valid ProxyProtocolVersion values are + "V1" + "V2" + enum: + - V1 + - V2 + type: string + required: + - version + type: object + retry: + description: |- + Retry provides more advanced usage, allowing users to customize the number of retries, retry fallback strategy, and retry triggering conditions. + If not set, retry will be disabled. + properties: + numRetries: + default: 2 + description: NumRetries is the number of retries + to be attempted. Defaults to 2. + format: int32 + minimum: 0 + type: integer + perRetry: + description: PerRetry is the retry policy to be + applied per retry attempt. + properties: + backOff: + description: |- + Backoff is the backoff policy to be applied per retry attempt. gateway uses a fully jittered exponential + back-off algorithm for retries. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#config-http-filters-router-x-envoy-max-retries + properties: + baseInterval: + description: BaseInterval is the base + interval between retries. + format: duration + type: string + maxInterval: + description: |- + MaxInterval is the maximum interval between retries. This parameter is optional, but must be greater than or equal to the base_interval if set. + The default is 10 times the base_interval + format: duration + type: string + type: object + timeout: + description: Timeout is the timeout per retry + attempt. + format: duration + type: string + type: object + retryOn: + description: |- + RetryOn specifies the retry trigger condition. + + If not specified, the default is to retry on connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes(503). + properties: + httpStatusCodes: + description: |- + HttpStatusCodes specifies the http status codes to be retried. + The retriable-status-codes trigger must also be configured for these status codes to trigger a retry. + items: + description: HTTPStatus defines the http + status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + triggers: + description: Triggers specifies the retry + trigger condition(Http/Grpc). + items: + description: TriggerEnum specifies the conditions + that trigger retries. + enum: + - 5xx + - gateway-error + - reset + - connect-failure + - retriable-4xx + - refused-stream + - retriable-status-codes + - cancelled + - deadline-exceeded + - internal + - resource-exhausted + - unavailable + type: string + type: array + type: object + type: object + tcpKeepalive: + description: |- + TcpKeepalive settings associated with the upstream client connection. + Disabled by default. + properties: + idleTime: + description: |- + The duration a connection needs to be idle before keep-alive + probes start being sent. + The duration format is + Defaults to `7200s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + The duration between keep-alive probes. + Defaults to `75s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + probes: + description: |- + The total number of unacknowledged probes to send before deciding + the connection is dead. + Defaults to 9. + format: int32 + type: integer + type: object + timeout: + description: Timeout settings for the backend connections. + properties: + http: + description: Timeout settings for HTTP. + properties: + connectionIdleTimeout: + description: |- + The idle timeout for an HTTP connection. Idle time is defined as a period in which there are no active requests in the connection. + Default: 1 hour. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + maxConnectionDuration: + description: |- + The maximum duration of an HTTP connection. + Default: unlimited. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + requestTimeout: + description: RequestTimeout is the time until + which entire response is received from the + upstream. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + tcp: + description: Timeout settings for TCP. + properties: + connectTimeout: + description: |- + The timeout for network connection establishment, including TCP and TLS handshakes. + Default: 10 seconds. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + type: object + type: object + host: + description: |- + Host define the provider service hostname. + Deprecated: Use BackendRefs instead. + type: string + port: + default: 4317 + description: |- + Port defines the port the provider service is exposed on. + Deprecated: Use BackendRefs instead. + format: int32 + minimum: 0 + type: integer + type: + default: OpenTelemetry + description: Type defines the tracing provider type. + enum: + - OpenTelemetry + - Zipkin + - Datadog + type: string + zipkin: + description: Zipkin defines the Zipkin tracing provider + configuration + properties: + disableSharedSpanContext: + description: |- + DisableSharedSpanContext determines whether the default Envoy behaviour of + client and server spans sharing the same span context should be disabled. + type: boolean + enable128BitTraceId: + description: |- + Enable128BitTraceID determines whether a 128bit trace id will be used + when creating a new trace instance. If set to false, a 64bit trace + id will be used. + type: boolean + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: host or backendRefs needs to be set + rule: has(self.host) || self.backendRefs.size() > 0 + - message: BackendRefs must be used, backendRef is not supported. + rule: '!has(self.backendRef)' + - message: BackendRefs only support Service and Backend kind. + rule: 'has(self.backendRefs) ? self.backendRefs.all(f, f.kind + == ''Service'' || f.kind == ''Backend'') : true' + - message: BackendRefs only support Core and gateway.envoyproxy.io + group. + rule: 'has(self.backendRefs) ? (self.backendRefs.all(f, + f.group == "" || f.group == ''gateway.envoyproxy.io'')) + : true' + samplingFraction: + description: |- + SamplingFraction represents the fraction of requests that should be + selected for tracing if no prior sampling decision has been made. + + Only one of SamplingRate or SamplingFraction may be specified. + If neither field is specified, all requests will be sampled. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal to denominator + rule: self.numerator <= self.denominator + samplingRate: + description: |- + SamplingRate controls the rate at which traffic will be + selected for tracing if no prior sampling decision has been made. + Defaults to 100, valid values [0-100]. 100 indicates 100% sampling. + + Only one of SamplingRate or SamplingFraction may be specified. + If neither field is specified, all requests will be sampled. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - provider + type: object + x-kubernetes-validations: + - message: only one of SamplingRate or SamplingFraction can be + specified + rule: '!(has(self.samplingRate) && has(self.samplingFraction))' + type: object + type: object + status: + description: EnvoyProxyStatus defines the actual state of EnvoyProxy. + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +# Source: gateway-crds-helm/templates/generated/gateway.envoyproxy.io_httproutefilters.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 + name: httproutefilters.gateway.envoyproxy.io +spec: + group: gateway.envoyproxy.io + names: + categories: + - envoy-gateway + kind: HTTPRouteFilter + listKind: HTTPRouteFilterList + plural: httproutefilters + shortNames: + - hrf + singular: httproutefilter + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + HTTPRouteFilter is a custom Envoy Gateway HTTPRouteFilter which provides extended + traffic processing options such as path regex rewrite, direct response and more. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of HTTPRouteFilter. + properties: + credentialInjection: + description: |- + HTTPCredentialInjectionFilter defines the configuration to inject credentials into the request. + This is useful when the backend service requires credentials in the request, and the original + request does not contain them. The filter can inject credentials into the request before forwarding + it to the backend service. + properties: + credential: + description: Credential is the credential to be injected. + properties: + valueRef: + description: |- + ValueRef is a reference to the secret containing the credentials to be injected. + This is an Opaque secret. The credential should be stored in the key + "credential", and the value should be the credential to be injected. + For example, for basic authentication, the value should be "Basic ". + for bearer token, the value should be "Bearer ". + Note: The secret must be in the same namespace as the HTTPRouteFilter. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example + "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + required: + - valueRef + type: object + header: + description: |- + Header is the name of the header where the credentials are injected. + If not specified, the credentials are injected into the Authorization header. + type: string + overwrite: + description: |- + Whether to overwrite the value or not if the injected headers already exist. + If not specified, the default value is false. + type: boolean + required: + - credential + type: object + directResponse: + description: HTTPDirectResponseFilter defines the configuration to + return a fixed response. + properties: + body: + description: Body of the Response + properties: + inline: + description: Inline contains the value as an inline string. + type: string + type: + allOf: + - enum: + - Inline + - ValueRef + - enum: + - Inline + - ValueRef + default: Inline + description: |- + Type is the type of method to use to read the body value. + Valid values are Inline and ValueRef, default is Inline. + type: string + valueRef: + description: |- + ValueRef contains the contents of the body + specified as a local object reference. + Only a reference to ConfigMap is supported. + + The value of key `response.body` in the ConfigMap will be used as the response body. + If the key is not found, the first value in the ConfigMap will be used. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: inline must be set for type Inline + rule: '(!has(self.type) || self.type == ''Inline'')? has(self.inline) + : true' + - message: valueRef must be set for type ValueRef + rule: '(has(self.type) && self.type == ''ValueRef'')? has(self.valueRef) + : true' + - message: only ConfigMap is supported for ValueRef + rule: 'has(self.valueRef) ? self.valueRef.kind == ''ConfigMap'' + : true' + contentType: + description: Content Type of the response. This will be set in + the Content-Type header. + type: string + statusCode: + description: |- + Status Code of the HTTP response + If unset, defaults to 200. + type: integer + type: object + urlRewrite: + description: HTTPURLRewriteFilter define rewrites of HTTP URL components + such as path and host + properties: + hostname: + description: |- + Hostname is the value to be used to replace the Host header value during + forwarding. + properties: + header: + description: Header is the name of the header whose value + would be used to rewrite the Host header + type: string + type: + description: HTTPPathModifierType defines the type of Hostname + rewrite. + enum: + - Header + - Backend + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: header must be nil if the type is not Header + rule: '!(has(self.header) && self.type != ''Header'')' + - message: header must be specified for Header type + rule: '!(!has(self.header) && self.type == ''Header'')' + path: + description: Path defines a path rewrite. + properties: + replaceRegexMatch: + description: |- + ReplaceRegexMatch defines a path regex rewrite. The path portions matched by the regex pattern are replaced by the defined substitution. + https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-routeaction-regex-rewrite + Some examples: + (1) replaceRegexMatch: + pattern: ^/service/([^/]+)(/.*)$ + substitution: \2/instance/\1 + Would transform /service/foo/v1/api into /v1/api/instance/foo. + (2) replaceRegexMatch: + pattern: one + substitution: two + Would transform /xxx/one/yyy/one/zzz into /xxx/two/yyy/two/zzz. + (3) replaceRegexMatch: + pattern: ^(.*?)one(.*)$ + substitution: \1two\2 + Would transform /xxx/one/yyy/one/zzz into /xxx/two/yyy/one/zzz. + (3) replaceRegexMatch: + pattern: (?i)/xxx/ + substitution: /yyy/ + Would transform path /aaa/XxX/bbb into /aaa/yyy/bbb (case-insensitive). + properties: + pattern: + description: |- + Pattern matches a regular expression against the value of the HTTP Path.The regex string must + adhere to the syntax documented in https://github.com/google/re2/wiki/Syntax. + minLength: 1 + type: string + substitution: + description: |- + Substitution is an expression that replaces the matched portion.The expression may include numbered + capture groups that adhere to syntax documented in https://github.com/google/re2/wiki/Syntax. + type: string + required: + - pattern + - substitution + type: object + type: + description: HTTPPathModifierType defines the type of path + redirect or rewrite. + enum: + - ReplaceRegexMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If HTTPPathModifier type is ReplaceRegexMatch, replaceRegexMatch + field needs to be set. + rule: 'self.type == ''ReplaceRegexMatch'' ? has(self.replaceRegexMatch) + : !has(self.replaceRegexMatch)' + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: {} +--- +# Source: gateway-crds-helm/templates/generated/gateway.envoyproxy.io_securitypolicies.yaml +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.3 + name: securitypolicies.gateway.envoyproxy.io +spec: + group: gateway.envoyproxy.io + names: + categories: + - envoy-gateway + kind: SecurityPolicy + listKind: SecurityPolicyList + plural: securitypolicies + shortNames: + - sp + singular: securitypolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + SecurityPolicy allows the user to configure various security settings for a + Gateway. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of SecurityPolicy. + properties: + apiKeyAuth: + description: APIKeyAuth defines the configuration for the API Key + Authentication. + properties: + credentialRefs: + description: |- + CredentialRefs is the Kubernetes secret which contains the API keys. + This is an Opaque secret. + Each API key is stored in the key representing the client id. + If the secrets have a key for a duplicated client, the first one will be used. + items: + description: |- + SecretObjectReference identifies an API object including its namespace, + defaulting to Secret. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + type: array + extractFrom: + description: |- + ExtractFrom is where to fetch the key from the coming request. + The value from the first source that has a key will be used. + items: + description: |- + ExtractFrom is where to fetch the key from the coming request. + Only one of header, param or cookie is supposed to be specified. + properties: + cookies: + description: |- + Cookies is the names of the cookie to fetch the key from. + If multiple cookies are specified, envoy will look for the api key in the order of the list. + This field is optional, but only one of headers, params or cookies is supposed to be specified. + items: + type: string + type: array + headers: + description: |- + Headers is the names of the header to fetch the key from. + If multiple headers are specified, envoy will look for the api key in the order of the list. + This field is optional, but only one of headers, params or cookies is supposed to be specified. + items: + type: string + type: array + params: + description: |- + Params is the names of the query parameter to fetch the key from. + If multiple params are specified, envoy will look for the api key in the order of the list. + This field is optional, but only one of headers, params or cookies is supposed to be specified. + items: + type: string + type: array + type: object + type: array + required: + - credentialRefs + - extractFrom + type: object + authorization: + description: Authorization defines the authorization configuration. + properties: + defaultAction: + description: |- + DefaultAction defines the default action to be taken if no rules match. + If not specified, the default action is Deny. + enum: + - Allow + - Deny + type: string + rules: + description: |- + Rules defines a list of authorization rules. + These rules are evaluated in order, the first matching rule will be applied, + and the rest will be skipped. + + For example, if there are two rules: the first rule allows the request + and the second rule denies it, when a request matches both rules, it will be allowed. + items: + description: AuthorizationRule defines a single authorization + rule. + properties: + action: + description: Action defines the action to be taken if the + rule matches. + enum: + - Allow + - Deny + type: string + name: + description: |- + Name is a user-friendly name for the rule. + If not specified, Envoy Gateway will generate a unique name for the rule. + maxLength: 253 + minLength: 1 + type: string + operation: + description: |- + Operation specifies the operation of a request, such as HTTP methods. + If not specified, all operations are matched on. + properties: + methods: + description: |- + Methods are the HTTP methods of the request. + If multiple methods are specified, all specified methods are allowed or denied, based on the action of the rule. + items: + description: |- + HTTPMethod describes how to select a HTTP route by matching the HTTP + method as defined by + [RFC 7231](https://datatracker.ietf.org/doc/html/rfc7231#section-4) and + [RFC 5789](https://datatracker.ietf.org/doc/html/rfc5789#section-2). + The value is expected in upper case. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + maxItems: 16 + minItems: 1 + type: array + required: + - methods + type: object + principal: + description: |- + Principal specifies the client identity of a request. + If there are multiple principal types, all principals must match for the rule to match. + For example, if there are two principals: one for client IP and one for JWT claim, + the rule will match only if both the client IP and the JWT claim match. + properties: + clientCIDRs: + description: |- + ClientCIDRs are the IP CIDR ranges of the client. + Valid examples are "192.168.1.0/24" or "2001:db8::/64" + + If multiple CIDR ranges are specified, one of the CIDR ranges must match + the client IP for the rule to match. + + The client IP is inferred from the X-Forwarded-For header, a custom header, + or the proxy protocol. + You can use the `ClientIPDetection` or the `EnableProxyProtocol` field in + the `ClientTrafficPolicy` to configure how the client IP is detected. + items: + description: |- + CIDR defines a CIDR Address range. + A CIDR can be an IPv4 address range such as "192.168.1.0/24" or an IPv6 address range such as "2001:0db8:11a3:09d7::/64". + pattern: ((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/([0-9]+))|((([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/([0-9]+)) + type: string + minItems: 1 + type: array + headers: + description: |- + Headers authorize the request based on user identity extracted from custom headers. + If multiple headers are specified, all headers must match for the rule to match. + items: + description: AuthorizationHeaderMatch specifies how + to match against the value of an HTTP header within + a authorization rule. + properties: + name: + description: |- + Name of the HTTP header. + The header name is case-insensitive unless PreserveHeaderCase is set to true. + For example, "Foo" and "foo" are considered the same header. + maxLength: 256 + minLength: 1 + type: string + values: + description: |- + Values are the values that the header must match. + If multiple values are specified, the rule will match if any of the values match. + items: + type: string + maxItems: 256 + minItems: 1 + type: array + required: + - name + - values + type: object + maxItems: 256 + minItems: 1 + type: array + jwt: + description: |- + JWT authorize the request based on the JWT claims and scopes. + Note: in order to use JWT claims for authorization, you must configure the + JWT authentication in the same `SecurityPolicy`. + properties: + claims: + description: |- + Claims are the claims in a JWT token. + + If multiple claims are specified, all claims must match for the rule to match. + For example, if there are two claims: one for the audience and one for the issuer, + the rule will match only if both the audience and the issuer match. + items: + description: JWTClaim specifies a claim in a JWT + token. + properties: + name: + description: |- + Name is the name of the claim. + If it is a nested claim, use a dot (.) separated string as the name to + represent the full path to the claim. + For example, if the claim is in the "department" field in the "organization" field, + the name should be "organization.department". + maxLength: 253 + minLength: 1 + type: string + valueType: + default: String + description: |- + ValueType is the type of the claim value. + Only String and StringArray types are supported for now. + enum: + - String + - StringArray + type: string + values: + description: |- + Values are the values that the claim must match. + If the claim is a string type, the specified value must match exactly. + If the claim is a string array type, the specified value must match one of the values in the array. + If multiple values are specified, one of the values must match for the rule to match. + items: + type: string + maxItems: 16 + minItems: 1 + type: array + required: + - name + - values + type: object + maxItems: 16 + minItems: 1 + type: array + provider: + description: |- + Provider is the name of the JWT provider that used to verify the JWT token. + In order to use JWT claims for authorization, you must configure the JWT + authentication with the same provider in the same `SecurityPolicy`. + maxLength: 253 + minLength: 1 + type: string + scopes: + description: |- + Scopes are a special type of claim in a JWT token that represents the permissions of the client. + + The value of the scopes field should be a space delimited string that is expected in the scope parameter, + as defined in RFC 6749: https://datatracker.ietf.org/doc/html/rfc6749#page-23. + + If multiple scopes are specified, all scopes must match for the rule to match. + items: + maxLength: 253 + minLength: 1 + type: string + maxItems: 16 + minItems: 1 + type: array + required: + - provider + type: object + x-kubernetes-validations: + - message: at least one of claims or scopes must be + specified + rule: (has(self.claims) || has(self.scopes)) + type: object + x-kubernetes-validations: + - message: at least one of clientCIDRs, jwt, or headers + must be specified + rule: (has(self.clientCIDRs) || has(self.jwt) || has(self.headers)) + required: + - action + - principal + type: object + type: array + type: object + basicAuth: + description: BasicAuth defines the configuration for the HTTP Basic + Authentication. + properties: + forwardUsernameHeader: + description: |- + This field specifies the header name to forward a successfully authenticated user to + the backend. The header will be added to the request with the username as the value. + + If it is not specified, the username will not be forwarded. + type: string + users: + description: |- + The Kubernetes secret which contains the username-password pairs in + htpasswd format, used to verify user credentials in the "Authorization" + header. + + This is an Opaque secret. The username-password pairs should be stored in + the key ".htpasswd". As the key name indicates, the value needs to be the + htpasswd format, for example: "user1:{SHA}hashed_user1_password". + Right now, only SHA hash algorithm is supported. + Reference to https://httpd.apache.org/docs/2.4/programs/htpasswd.html + for more details. + + Note: The secret must be in the same namespace as the SecurityPolicy. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + required: + - users + type: object + cors: + description: CORS defines the configuration for Cross-Origin Resource + Sharing (CORS). + properties: + allowCredentials: + description: |- + AllowCredentials indicates whether a request can include user credentials + like cookies, authentication headers, or TLS client certificates. + It specifies the value in the Access-Control-Allow-Credentials CORS response header. + type: boolean + allowHeaders: + description: |- + AllowHeaders defines the headers that are allowed to be sent with requests. + It specifies the allowed headers in the Access-Control-Allow-Headers CORS response header.. + The value "*" allows any header to be sent. + items: + type: string + type: array + allowMethods: + description: |- + AllowMethods defines the methods that are allowed to make requests. + It specifies the allowed methods in the Access-Control-Allow-Methods CORS response header.. + The value "*" allows any method to be used. + items: + type: string + type: array + allowOrigins: + description: |- + AllowOrigins defines the origins that are allowed to make requests. + It specifies the allowed origins in the Access-Control-Allow-Origin CORS response header. + The value "*" allows any origin to make requests. + items: + description: |- + Origin is defined by the scheme (protocol), hostname (domain), and port of + the URL used to access it. The hostname can be "precise" which is just the + domain name or "wildcard" which is a domain name prefixed with a single + wildcard label such as "*.example.com". + In addition to that a single wildcard (with or without scheme) can be + configured to match any origin. + + For example, the following are valid origins: + - https://foo.example.com + - https://*.example.com + - http://foo.example.com:8080 + - http://*.example.com:8080 + - https://* + maxLength: 253 + minLength: 1 + pattern: ^(\*|https?:\/\/(\*|(\*\.)?(([\w-]+\.?)+)?[\w-]+)(:\d{1,5})?)$ + type: string + type: array + exposeHeaders: + description: |- + ExposeHeaders defines which response headers should be made accessible to + scripts running in the browser. + It specifies the headers in the Access-Control-Expose-Headers CORS response header.. + The value "*" allows any header to be exposed. + items: + type: string + type: array + maxAge: + description: |- + MaxAge defines how long the results of a preflight request can be cached. + It specifies the value in the Access-Control-Max-Age CORS response header.. + type: string + type: object + extAuth: + description: ExtAuth defines the configuration for External Authorization. + properties: + bodyToExtAuth: + description: BodyToExtAuth defines the Body to Ext Auth configuration. + properties: + maxRequestBytes: + description: |- + MaxRequestBytes is the maximum size of a message body that the filter will hold in memory. + Envoy will return HTTP 413 and will not initiate the authorization process when buffer + reaches the number set in this field. + Note that this setting will have precedence over failOpen mode. + format: int32 + minimum: 1 + type: integer + required: + - maxRequestBytes + type: object + failOpen: + default: false + description: |- + FailOpen is a switch used to control the behavior when a response from the External Authorization service cannot be obtained. + If FailOpen is set to true, the system allows the traffic to pass through. + Otherwise, if it is set to false or not set (defaulting to false), + the system blocks the traffic and returns a HTTP 5xx error, reflecting a fail-closed approach. + This setting determines whether to prioritize accessibility over strict security in case of authorization service failure. + type: boolean + grpc: + description: |- + GRPC defines the gRPC External Authorization service. + Either GRPCService or HTTPService must be specified, + and only one of them can be provided. + properties: + backendRef: + description: |- + BackendRef references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + + Deprecated: Use BackendRefs instead. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + backendRefs: + description: |- + BackendRefs references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + items: + description: BackendRef defines how an ObjectReference that + is specific to BackendRef. + properties: + fallback: + description: |- + Fallback indicates whether the backend is designated as a fallback. + Multiple fallback backends can be configured. + It is highly recommended to configure active or passive health checks to ensure that failover can be detected + when the active backends become unhealthy and to automatically readjust once the primary backends are healthy again. + The overprovisioning factor is set to 1.4, meaning the fallback backends will only start receiving traffic when + the health of the active backends falls below 72%. + type: boolean + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + backendSettings: + description: |- + BackendSettings holds configuration for managing the connection + to the backend. + properties: + circuitBreaker: + description: |- + Circuit Breaker settings for the upstream connections and requests. + If not set, circuit breakers will be enabled with the default thresholds + properties: + maxConnections: + default: 1024 + description: The maximum number of connections that + Envoy will establish to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRequests: + default: 1024 + description: The maximum number of parallel requests + that Envoy will make to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRetries: + default: 1024 + description: The maximum number of parallel retries + that Envoy will make to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxPendingRequests: + default: 1024 + description: The maximum number of pending requests + that Envoy will queue to the referenced backend + defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxRequestsPerConnection: + description: |- + The maximum number of requests that Envoy will make over a single connection to the referenced backend defined within a xRoute rule. + Default: unlimited. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + perEndpoint: + description: PerEndpoint defines Circuit Breakers + that will apply per-endpoint for an upstream cluster + properties: + maxConnections: + default: 1024 + description: MaxConnections configures the maximum + number of connections that Envoy will establish + per-endpoint to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + type: object + type: object + connection: + description: Connection includes backend connection settings. + properties: + bufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + BufferLimit Soft limit on size of the cluster’s connections read and write buffers. + BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space. + If unspecified, an implementation defined default is applied (32768 bytes). + For example, 20Mi, 1Gi, 256Ki etc. + Note: that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + socketBufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + SocketBufferLimit provides configuration for the maximum buffer size in bytes for each socket + to backend. + SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space. + For example, 20Mi, 1Gi, 256Ki etc. + Note that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + type: object + dns: + description: DNS includes dns resolution settings. + properties: + dnsRefreshRate: + description: |- + DNSRefreshRate specifies the rate at which DNS records should be refreshed. + Defaults to 30 seconds. + type: string + lookupFamily: + description: |- + LookupFamily determines how Envoy would resolve DNS for Routes where the backend is specified as a fully qualified domain name (FQDN). + If set, this configuration overrides other defaults. + enum: + - IPv4 + - IPv6 + - IPv4Preferred + - IPv6Preferred + - IPv4AndIPv6 + type: string + respectDnsTtl: + description: |- + RespectDNSTTL indicates whether the DNS Time-To-Live (TTL) should be respected. + If the value is set to true, the DNS refresh rate will be set to the resource record’s TTL. + Defaults to true. + type: boolean + type: object + healthCheck: + description: HealthCheck allows gateway to perform active + health checking on backends. + properties: + active: + description: Active health check configuration + properties: + grpc: + description: |- + GRPC defines the configuration of the GRPC health checker. + It's optional, and can only be used if the specified type is GRPC. + properties: + service: + description: |- + Service to send in the health check request. + If this is not specified, then the health check request applies to the entire + server and not to a specific service. + type: string + type: object + healthyThreshold: + default: 1 + description: HealthyThreshold defines the number + of healthy health checks required before a backend + host is marked healthy. + format: int32 + minimum: 1 + type: integer + http: + description: |- + HTTP defines the configuration of http health checker. + It's required while the health checker type is HTTP. + properties: + expectedResponse: + description: ExpectedResponse defines a list + of HTTP expected responses to match. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of + the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, binary + field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + expectedStatuses: + description: |- + ExpectedStatuses defines a list of HTTP response statuses considered healthy. + Defaults to 200 only + items: + description: HTTPStatus defines the http + status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + method: + description: |- + Method defines the HTTP method used for health checking. + Defaults to GET + type: string + path: + description: Path defines the HTTP path that + will be requested during health checking. + maxLength: 1024 + minLength: 1 + type: string + required: + - path + type: object + interval: + default: 3s + description: Interval defines the time between + active health checks. + format: duration + type: string + tcp: + description: |- + TCP defines the configuration of tcp health checker. + It's required while the health checker type is TCP. + properties: + receive: + description: Receive defines the expected + response payload. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of + the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, binary + field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + send: + description: Send defines the request payload. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of + the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, binary + field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + type: object + timeout: + default: 1s + description: Timeout defines the time to wait + for a health check response. + format: duration + type: string + type: + allOf: + - enum: + - HTTP + - TCP + - GRPC + - enum: + - HTTP + - TCP + - GRPC + description: Type defines the type of health checker. + type: string + unhealthyThreshold: + default: 3 + description: UnhealthyThreshold defines the number + of unhealthy health checks required before a + backend host is marked unhealthy. + format: int32 + minimum: 1 + type: integer + required: + - type + type: object + x-kubernetes-validations: + - message: If Health Checker type is HTTP, http field + needs to be set. + rule: 'self.type == ''HTTP'' ? has(self.http) : + !has(self.http)' + - message: If Health Checker type is TCP, tcp field + needs to be set. + rule: 'self.type == ''TCP'' ? has(self.tcp) : !has(self.tcp)' + - message: The grpc field can only be set if the Health + Checker type is GRPC. + rule: 'has(self.grpc) ? self.type == ''GRPC'' : + true' + panicThreshold: + description: |- + When number of unhealthy endpoints for a backend reaches this threshold + Envoy will disregard health status and balance across all endpoints. + It's designed to prevent a situation in which host failures cascade throughout the cluster + as load increases. If not set, the default value is 50%. To disable panic mode, set value to `0`. + format: int32 + maximum: 100 + minimum: 0 + type: integer + passive: + description: Passive passive check configuration + properties: + baseEjectionTime: + default: 30s + description: BaseEjectionTime defines the base + duration for which a host will be ejected on + consecutive failures. + format: duration + type: string + consecutive5XxErrors: + default: 5 + description: Consecutive5xxErrors sets the number + of consecutive 5xx errors triggering ejection. + format: int32 + type: integer + consecutiveGatewayErrors: + default: 0 + description: ConsecutiveGatewayErrors sets the + number of consecutive gateway errors triggering + ejection. + format: int32 + type: integer + consecutiveLocalOriginFailures: + default: 5 + description: |- + ConsecutiveLocalOriginFailures sets the number of consecutive local origin failures triggering ejection. + Parameter takes effect only when split_external_local_origin_errors is set to true. + format: int32 + type: integer + interval: + default: 3s + description: Interval defines the time between + passive health checks. + format: duration + type: string + maxEjectionPercent: + default: 10 + description: MaxEjectionPercent sets the maximum + percentage of hosts in a cluster that can be + ejected. + format: int32 + type: integer + splitExternalLocalOriginErrors: + default: false + description: SplitExternalLocalOriginErrors enables + splitting of errors between external and local + origin. + type: boolean + type: object + type: object + http2: + description: HTTP2 provides HTTP/2 configuration for backend + connections. + properties: + initialConnectionWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialConnectionWindowSize sets the initial window size for HTTP/2 connections. + If not set, the default value is 1 MiB. + x-kubernetes-int-or-string: true + initialStreamWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialStreamWindowSize sets the initial window size for HTTP/2 streams. + If not set, the default value is 64 KiB(64*1024). + x-kubernetes-int-or-string: true + maxConcurrentStreams: + description: |- + MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection. + If not set, the default value is 100. + format: int32 + maximum: 2147483647 + minimum: 1 + type: integer + onInvalidMessage: + description: |- + OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error + It's recommended for L2 Envoy deployments to set this value to TerminateStream. + https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two + Default: TerminateConnection + type: string + type: object + loadBalancer: + description: |- + LoadBalancer policy to apply when routing traffic from the gateway to + the backend endpoints. Defaults to `LeastRequest`. + properties: + consistentHash: + description: |- + ConsistentHash defines the configuration when the load balancer type is + set to ConsistentHash + properties: + cookie: + description: Cookie configures the cookie hash + policy when the consistent hash type is set + to Cookie. + properties: + attributes: + additionalProperties: + type: string + description: Additional Attributes to set + for the generated cookie. + type: object + name: + description: |- + Name of the cookie to hash. + If this cookie does not exist in the request, Envoy will generate a cookie and set + the TTL on the response back to the client based on Layer 4 + attributes of the backend endpoint, to ensure that these future requests + go to the same backend endpoint. Make sure to set the TTL field for this case. + type: string + ttl: + description: |- + TTL of the generated cookie if the cookie is not present. This value sets the + Max-Age attribute value. + type: string + required: + - name + type: object + header: + description: Header configures the header hash + policy when the consistent hash type is set + to Header. + properties: + name: + description: Name of the header to hash. + type: string + required: + - name + type: object + tableSize: + default: 65537 + description: The table size for consistent hashing, + must be prime number limited to 5000011. + format: int64 + maximum: 5000011 + minimum: 2 + type: integer + type: + description: |- + ConsistentHashType defines the type of input to hash on. Valid Type values are + "SourceIP", + "Header", + "Cookie". + enum: + - SourceIP + - Header + - Cookie + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If consistent hash type is header, the + header field must be set. + rule: 'self.type == ''Header'' ? has(self.header) + : !has(self.header)' + - message: If consistent hash type is cookie, the + cookie field must be set. + rule: 'self.type == ''Cookie'' ? has(self.cookie) + : !has(self.cookie)' + slowStart: + description: |- + SlowStart defines the configuration related to the slow start load balancer policy. + If set, during slow start window, traffic sent to the newly added hosts will gradually increase. + Currently this is only supported for RoundRobin and LeastRequest load balancers + properties: + window: + description: |- + Window defines the duration of the warm up period for newly added host. + During slow start window, traffic sent to the newly added hosts will gradually increase. + Currently only supports linear growth of traffic. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#config-cluster-v3-cluster-slowstartconfig + type: string + required: + - window + type: object + type: + description: |- + Type decides the type of Load Balancer policy. + Valid LoadBalancerType values are + "ConsistentHash", + "LeastRequest", + "Random", + "RoundRobin". + enum: + - ConsistentHash + - LeastRequest + - Random + - RoundRobin + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If LoadBalancer type is consistentHash, consistentHash + field needs to be set. + rule: 'self.type == ''ConsistentHash'' ? has(self.consistentHash) + : !has(self.consistentHash)' + - message: Currently SlowStart is only supported for RoundRobin + and LeastRequest load balancers. + rule: 'self.type in [''Random'', ''ConsistentHash''] + ? !has(self.slowStart) : true ' + proxyProtocol: + description: ProxyProtocol enables the Proxy Protocol + when communicating with the backend. + properties: + version: + description: |- + Version of ProxyProtol + Valid ProxyProtocolVersion values are + "V1" + "V2" + enum: + - V1 + - V2 + type: string + required: + - version + type: object + retry: + description: |- + Retry provides more advanced usage, allowing users to customize the number of retries, retry fallback strategy, and retry triggering conditions. + If not set, retry will be disabled. + properties: + numRetries: + default: 2 + description: NumRetries is the number of retries to + be attempted. Defaults to 2. + format: int32 + minimum: 0 + type: integer + perRetry: + description: PerRetry is the retry policy to be applied + per retry attempt. + properties: + backOff: + description: |- + Backoff is the backoff policy to be applied per retry attempt. gateway uses a fully jittered exponential + back-off algorithm for retries. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#config-http-filters-router-x-envoy-max-retries + properties: + baseInterval: + description: BaseInterval is the base interval + between retries. + format: duration + type: string + maxInterval: + description: |- + MaxInterval is the maximum interval between retries. This parameter is optional, but must be greater than or equal to the base_interval if set. + The default is 10 times the base_interval + format: duration + type: string + type: object + timeout: + description: Timeout is the timeout per retry + attempt. + format: duration + type: string + type: object + retryOn: + description: |- + RetryOn specifies the retry trigger condition. + + If not specified, the default is to retry on connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes(503). + properties: + httpStatusCodes: + description: |- + HttpStatusCodes specifies the http status codes to be retried. + The retriable-status-codes trigger must also be configured for these status codes to trigger a retry. + items: + description: HTTPStatus defines the http status + code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + triggers: + description: Triggers specifies the retry trigger + condition(Http/Grpc). + items: + description: TriggerEnum specifies the conditions + that trigger retries. + enum: + - 5xx + - gateway-error + - reset + - connect-failure + - retriable-4xx + - refused-stream + - retriable-status-codes + - cancelled + - deadline-exceeded + - internal + - resource-exhausted + - unavailable + type: string + type: array + type: object + type: object + tcpKeepalive: + description: |- + TcpKeepalive settings associated with the upstream client connection. + Disabled by default. + properties: + idleTime: + description: |- + The duration a connection needs to be idle before keep-alive + probes start being sent. + The duration format is + Defaults to `7200s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + The duration between keep-alive probes. + Defaults to `75s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + probes: + description: |- + The total number of unacknowledged probes to send before deciding + the connection is dead. + Defaults to 9. + format: int32 + type: integer + type: object + timeout: + description: Timeout settings for the backend connections. + properties: + http: + description: Timeout settings for HTTP. + properties: + connectionIdleTimeout: + description: |- + The idle timeout for an HTTP connection. Idle time is defined as a period in which there are no active requests in the connection. + Default: 1 hour. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + maxConnectionDuration: + description: |- + The maximum duration of an HTTP connection. + Default: unlimited. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + requestTimeout: + description: RequestTimeout is the time until + which entire response is received from the upstream. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + tcp: + description: Timeout settings for TCP. + properties: + connectTimeout: + description: |- + The timeout for network connection establishment, including TCP and TLS handshakes. + Default: 10 seconds. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + type: object + type: object + type: object + x-kubernetes-validations: + - message: backendRef or backendRefs needs to be set + rule: has(self.backendRef) || self.backendRefs.size() > 0 + - message: BackendRefs only supports Service and Backend kind. + rule: 'has(self.backendRefs) ? self.backendRefs.all(f, f.kind + == ''Service'' || f.kind == ''Backend'') : true' + - message: BackendRefs only supports Core and gateway.envoyproxy.io + group. + rule: 'has(self.backendRefs) ? (self.backendRefs.all(f, f.group + == "" || f.group == ''gateway.envoyproxy.io'')) : true' + headersToExtAuth: + description: |- + HeadersToExtAuth defines the client request headers that will be included + in the request to the external authorization service. + Note: If not specified, the default behavior for gRPC and HTTP external + authorization services is different due to backward compatibility reasons. + All headers will be included in the check request to a gRPC authorization server. + Only the following headers will be included in the check request to an HTTP + authorization server: Host, Method, Path, Content-Length, and Authorization. + And these headers will always be included to the check request to an HTTP + authorization server by default, no matter whether they are specified + in HeadersToExtAuth or not. + items: + type: string + type: array + http: + description: |- + HTTP defines the HTTP External Authorization service. + Either GRPCService or HTTPService must be specified, + and only one of them can be provided. + properties: + backendRef: + description: |- + BackendRef references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + + Deprecated: Use BackendRefs instead. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + backendRefs: + description: |- + BackendRefs references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + items: + description: BackendRef defines how an ObjectReference that + is specific to BackendRef. + properties: + fallback: + description: |- + Fallback indicates whether the backend is designated as a fallback. + Multiple fallback backends can be configured. + It is highly recommended to configure active or passive health checks to ensure that failover can be detected + when the active backends become unhealthy and to automatically readjust once the primary backends are healthy again. + The overprovisioning factor is set to 1.4, meaning the fallback backends will only start receiving traffic when + the health of the active backends falls below 72%. + type: boolean + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + backendSettings: + description: |- + BackendSettings holds configuration for managing the connection + to the backend. + properties: + circuitBreaker: + description: |- + Circuit Breaker settings for the upstream connections and requests. + If not set, circuit breakers will be enabled with the default thresholds + properties: + maxConnections: + default: 1024 + description: The maximum number of connections that + Envoy will establish to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRequests: + default: 1024 + description: The maximum number of parallel requests + that Envoy will make to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRetries: + default: 1024 + description: The maximum number of parallel retries + that Envoy will make to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxPendingRequests: + default: 1024 + description: The maximum number of pending requests + that Envoy will queue to the referenced backend + defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxRequestsPerConnection: + description: |- + The maximum number of requests that Envoy will make over a single connection to the referenced backend defined within a xRoute rule. + Default: unlimited. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + perEndpoint: + description: PerEndpoint defines Circuit Breakers + that will apply per-endpoint for an upstream cluster + properties: + maxConnections: + default: 1024 + description: MaxConnections configures the maximum + number of connections that Envoy will establish + per-endpoint to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + type: object + type: object + connection: + description: Connection includes backend connection settings. + properties: + bufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + BufferLimit Soft limit on size of the cluster’s connections read and write buffers. + BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space. + If unspecified, an implementation defined default is applied (32768 bytes). + For example, 20Mi, 1Gi, 256Ki etc. + Note: that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + socketBufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + SocketBufferLimit provides configuration for the maximum buffer size in bytes for each socket + to backend. + SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space. + For example, 20Mi, 1Gi, 256Ki etc. + Note that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + type: object + dns: + description: DNS includes dns resolution settings. + properties: + dnsRefreshRate: + description: |- + DNSRefreshRate specifies the rate at which DNS records should be refreshed. + Defaults to 30 seconds. + type: string + lookupFamily: + description: |- + LookupFamily determines how Envoy would resolve DNS for Routes where the backend is specified as a fully qualified domain name (FQDN). + If set, this configuration overrides other defaults. + enum: + - IPv4 + - IPv6 + - IPv4Preferred + - IPv6Preferred + - IPv4AndIPv6 + type: string + respectDnsTtl: + description: |- + RespectDNSTTL indicates whether the DNS Time-To-Live (TTL) should be respected. + If the value is set to true, the DNS refresh rate will be set to the resource record’s TTL. + Defaults to true. + type: boolean + type: object + healthCheck: + description: HealthCheck allows gateway to perform active + health checking on backends. + properties: + active: + description: Active health check configuration + properties: + grpc: + description: |- + GRPC defines the configuration of the GRPC health checker. + It's optional, and can only be used if the specified type is GRPC. + properties: + service: + description: |- + Service to send in the health check request. + If this is not specified, then the health check request applies to the entire + server and not to a specific service. + type: string + type: object + healthyThreshold: + default: 1 + description: HealthyThreshold defines the number + of healthy health checks required before a backend + host is marked healthy. + format: int32 + minimum: 1 + type: integer + http: + description: |- + HTTP defines the configuration of http health checker. + It's required while the health checker type is HTTP. + properties: + expectedResponse: + description: ExpectedResponse defines a list + of HTTP expected responses to match. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of + the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, binary + field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + expectedStatuses: + description: |- + ExpectedStatuses defines a list of HTTP response statuses considered healthy. + Defaults to 200 only + items: + description: HTTPStatus defines the http + status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + method: + description: |- + Method defines the HTTP method used for health checking. + Defaults to GET + type: string + path: + description: Path defines the HTTP path that + will be requested during health checking. + maxLength: 1024 + minLength: 1 + type: string + required: + - path + type: object + interval: + default: 3s + description: Interval defines the time between + active health checks. + format: duration + type: string + tcp: + description: |- + TCP defines the configuration of tcp health checker. + It's required while the health checker type is TCP. + properties: + receive: + description: Receive defines the expected + response payload. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of + the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, binary + field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + send: + description: Send defines the request payload. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of + the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, binary + field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + type: object + timeout: + default: 1s + description: Timeout defines the time to wait + for a health check response. + format: duration + type: string + type: + allOf: + - enum: + - HTTP + - TCP + - GRPC + - enum: + - HTTP + - TCP + - GRPC + description: Type defines the type of health checker. + type: string + unhealthyThreshold: + default: 3 + description: UnhealthyThreshold defines the number + of unhealthy health checks required before a + backend host is marked unhealthy. + format: int32 + minimum: 1 + type: integer + required: + - type + type: object + x-kubernetes-validations: + - message: If Health Checker type is HTTP, http field + needs to be set. + rule: 'self.type == ''HTTP'' ? has(self.http) : + !has(self.http)' + - message: If Health Checker type is TCP, tcp field + needs to be set. + rule: 'self.type == ''TCP'' ? has(self.tcp) : !has(self.tcp)' + - message: The grpc field can only be set if the Health + Checker type is GRPC. + rule: 'has(self.grpc) ? self.type == ''GRPC'' : + true' + panicThreshold: + description: |- + When number of unhealthy endpoints for a backend reaches this threshold + Envoy will disregard health status and balance across all endpoints. + It's designed to prevent a situation in which host failures cascade throughout the cluster + as load increases. If not set, the default value is 50%. To disable panic mode, set value to `0`. + format: int32 + maximum: 100 + minimum: 0 + type: integer + passive: + description: Passive passive check configuration + properties: + baseEjectionTime: + default: 30s + description: BaseEjectionTime defines the base + duration for which a host will be ejected on + consecutive failures. + format: duration + type: string + consecutive5XxErrors: + default: 5 + description: Consecutive5xxErrors sets the number + of consecutive 5xx errors triggering ejection. + format: int32 + type: integer + consecutiveGatewayErrors: + default: 0 + description: ConsecutiveGatewayErrors sets the + number of consecutive gateway errors triggering + ejection. + format: int32 + type: integer + consecutiveLocalOriginFailures: + default: 5 + description: |- + ConsecutiveLocalOriginFailures sets the number of consecutive local origin failures triggering ejection. + Parameter takes effect only when split_external_local_origin_errors is set to true. + format: int32 + type: integer + interval: + default: 3s + description: Interval defines the time between + passive health checks. + format: duration + type: string + maxEjectionPercent: + default: 10 + description: MaxEjectionPercent sets the maximum + percentage of hosts in a cluster that can be + ejected. + format: int32 + type: integer + splitExternalLocalOriginErrors: + default: false + description: SplitExternalLocalOriginErrors enables + splitting of errors between external and local + origin. + type: boolean + type: object + type: object + http2: + description: HTTP2 provides HTTP/2 configuration for backend + connections. + properties: + initialConnectionWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialConnectionWindowSize sets the initial window size for HTTP/2 connections. + If not set, the default value is 1 MiB. + x-kubernetes-int-or-string: true + initialStreamWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialStreamWindowSize sets the initial window size for HTTP/2 streams. + If not set, the default value is 64 KiB(64*1024). + x-kubernetes-int-or-string: true + maxConcurrentStreams: + description: |- + MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection. + If not set, the default value is 100. + format: int32 + maximum: 2147483647 + minimum: 1 + type: integer + onInvalidMessage: + description: |- + OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error + It's recommended for L2 Envoy deployments to set this value to TerminateStream. + https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two + Default: TerminateConnection + type: string + type: object + loadBalancer: + description: |- + LoadBalancer policy to apply when routing traffic from the gateway to + the backend endpoints. Defaults to `LeastRequest`. + properties: + consistentHash: + description: |- + ConsistentHash defines the configuration when the load balancer type is + set to ConsistentHash + properties: + cookie: + description: Cookie configures the cookie hash + policy when the consistent hash type is set + to Cookie. + properties: + attributes: + additionalProperties: + type: string + description: Additional Attributes to set + for the generated cookie. + type: object + name: + description: |- + Name of the cookie to hash. + If this cookie does not exist in the request, Envoy will generate a cookie and set + the TTL on the response back to the client based on Layer 4 + attributes of the backend endpoint, to ensure that these future requests + go to the same backend endpoint. Make sure to set the TTL field for this case. + type: string + ttl: + description: |- + TTL of the generated cookie if the cookie is not present. This value sets the + Max-Age attribute value. + type: string + required: + - name + type: object + header: + description: Header configures the header hash + policy when the consistent hash type is set + to Header. + properties: + name: + description: Name of the header to hash. + type: string + required: + - name + type: object + tableSize: + default: 65537 + description: The table size for consistent hashing, + must be prime number limited to 5000011. + format: int64 + maximum: 5000011 + minimum: 2 + type: integer + type: + description: |- + ConsistentHashType defines the type of input to hash on. Valid Type values are + "SourceIP", + "Header", + "Cookie". + enum: + - SourceIP + - Header + - Cookie + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If consistent hash type is header, the + header field must be set. + rule: 'self.type == ''Header'' ? has(self.header) + : !has(self.header)' + - message: If consistent hash type is cookie, the + cookie field must be set. + rule: 'self.type == ''Cookie'' ? has(self.cookie) + : !has(self.cookie)' + slowStart: + description: |- + SlowStart defines the configuration related to the slow start load balancer policy. + If set, during slow start window, traffic sent to the newly added hosts will gradually increase. + Currently this is only supported for RoundRobin and LeastRequest load balancers + properties: + window: + description: |- + Window defines the duration of the warm up period for newly added host. + During slow start window, traffic sent to the newly added hosts will gradually increase. + Currently only supports linear growth of traffic. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#config-cluster-v3-cluster-slowstartconfig + type: string + required: + - window + type: object + type: + description: |- + Type decides the type of Load Balancer policy. + Valid LoadBalancerType values are + "ConsistentHash", + "LeastRequest", + "Random", + "RoundRobin". + enum: + - ConsistentHash + - LeastRequest + - Random + - RoundRobin + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If LoadBalancer type is consistentHash, consistentHash + field needs to be set. + rule: 'self.type == ''ConsistentHash'' ? has(self.consistentHash) + : !has(self.consistentHash)' + - message: Currently SlowStart is only supported for RoundRobin + and LeastRequest load balancers. + rule: 'self.type in [''Random'', ''ConsistentHash''] + ? !has(self.slowStart) : true ' + proxyProtocol: + description: ProxyProtocol enables the Proxy Protocol + when communicating with the backend. + properties: + version: + description: |- + Version of ProxyProtol + Valid ProxyProtocolVersion values are + "V1" + "V2" + enum: + - V1 + - V2 + type: string + required: + - version + type: object + retry: + description: |- + Retry provides more advanced usage, allowing users to customize the number of retries, retry fallback strategy, and retry triggering conditions. + If not set, retry will be disabled. + properties: + numRetries: + default: 2 + description: NumRetries is the number of retries to + be attempted. Defaults to 2. + format: int32 + minimum: 0 + type: integer + perRetry: + description: PerRetry is the retry policy to be applied + per retry attempt. + properties: + backOff: + description: |- + Backoff is the backoff policy to be applied per retry attempt. gateway uses a fully jittered exponential + back-off algorithm for retries. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#config-http-filters-router-x-envoy-max-retries + properties: + baseInterval: + description: BaseInterval is the base interval + between retries. + format: duration + type: string + maxInterval: + description: |- + MaxInterval is the maximum interval between retries. This parameter is optional, but must be greater than or equal to the base_interval if set. + The default is 10 times the base_interval + format: duration + type: string + type: object + timeout: + description: Timeout is the timeout per retry + attempt. + format: duration + type: string + type: object + retryOn: + description: |- + RetryOn specifies the retry trigger condition. + + If not specified, the default is to retry on connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes(503). + properties: + httpStatusCodes: + description: |- + HttpStatusCodes specifies the http status codes to be retried. + The retriable-status-codes trigger must also be configured for these status codes to trigger a retry. + items: + description: HTTPStatus defines the http status + code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + triggers: + description: Triggers specifies the retry trigger + condition(Http/Grpc). + items: + description: TriggerEnum specifies the conditions + that trigger retries. + enum: + - 5xx + - gateway-error + - reset + - connect-failure + - retriable-4xx + - refused-stream + - retriable-status-codes + - cancelled + - deadline-exceeded + - internal + - resource-exhausted + - unavailable + type: string + type: array + type: object + type: object + tcpKeepalive: + description: |- + TcpKeepalive settings associated with the upstream client connection. + Disabled by default. + properties: + idleTime: + description: |- + The duration a connection needs to be idle before keep-alive + probes start being sent. + The duration format is + Defaults to `7200s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + The duration between keep-alive probes. + Defaults to `75s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + probes: + description: |- + The total number of unacknowledged probes to send before deciding + the connection is dead. + Defaults to 9. + format: int32 + type: integer + type: object + timeout: + description: Timeout settings for the backend connections. + properties: + http: + description: Timeout settings for HTTP. + properties: + connectionIdleTimeout: + description: |- + The idle timeout for an HTTP connection. Idle time is defined as a period in which there are no active requests in the connection. + Default: 1 hour. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + maxConnectionDuration: + description: |- + The maximum duration of an HTTP connection. + Default: unlimited. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + requestTimeout: + description: RequestTimeout is the time until + which entire response is received from the upstream. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + tcp: + description: Timeout settings for TCP. + properties: + connectTimeout: + description: |- + The timeout for network connection establishment, including TCP and TLS handshakes. + Default: 10 seconds. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + type: object + type: object + headersToBackend: + description: |- + HeadersToBackend are the authorization response headers that will be added + to the original client request before sending it to the backend server. + Note that coexisting headers will be overridden. + If not specified, no authorization response headers will be added to the + original client request. + items: + type: string + type: array + path: + description: |- + Path is the path of the HTTP External Authorization service. + If path is specified, the authorization request will be sent to that path, + or else the authorization request will use the path of the original request. + + Please note that the original request path will be appended to the path specified here. + For example, if the original request path is "/hello", and the path specified here is "/auth", + then the path of the authorization request will be "/auth/hello". If the path is not specified, + the path of the authorization request will be "/hello". + type: string + type: object + x-kubernetes-validations: + - message: backendRef or backendRefs needs to be set + rule: has(self.backendRef) || self.backendRefs.size() > 0 + - message: BackendRefs only supports Service and Backend kind. + rule: 'has(self.backendRefs) ? self.backendRefs.all(f, f.kind + == ''Service'' || f.kind == ''Backend'') : true' + - message: BackendRefs only supports Core and gateway.envoyproxy.io + group. + rule: 'has(self.backendRefs) ? (self.backendRefs.all(f, f.group + == "" || f.group == ''gateway.envoyproxy.io'')) : true' + recomputeRoute: + description: |- + RecomputeRoute clears the route cache and recalculates the routing decision. + This field must be enabled if the headers added or modified by the ExtAuth are used for + route matching decisions. If the recomputation selects a new route, features targeting + the new matched route will be applied. + type: boolean + type: object + x-kubernetes-validations: + - message: one of grpc or http must be specified + rule: (has(self.grpc) || has(self.http)) + - message: only one of grpc or http can be specified + rule: (has(self.grpc) && !has(self.http)) || (!has(self.grpc) && + has(self.http)) + jwt: + description: JWT defines the configuration for JSON Web Token (JWT) + authentication. + properties: + optional: + description: |- + Optional determines whether a missing JWT is acceptable, defaulting to false if not specified. + Note: Even if optional is set to true, JWT authentication will still fail if an invalid JWT is presented. + type: boolean + providers: + description: |- + Providers defines the JSON Web Token (JWT) authentication provider type. + When multiple JWT providers 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. + items: + description: JWTProvider defines how a JSON Web Token (JWT) + can be verified. + properties: + audiences: + description: |- + 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. + items: + type: string + maxItems: 8 + type: array + claimToHeaders: + description: |- + ClaimToHeaders is a list of JWT claims that must be extracted into HTTP request headers + For examples, following config: + The claim must be of type; string, int, double, bool. Array type claims are not supported + items: + description: ClaimToHeader defines a configuration to + convert JWT claims into HTTP headers + properties: + claim: + description: |- + Claim is the JWT Claim that should be saved into the header : it can be a nested claim of type + (eg. "claim.nested.key", "sub"). The nested claim name must use dot "." + to separate the JSON name path. + type: string + header: + description: Header defines the name of the HTTP request + header that the JWT Claim will be saved into. + type: string + required: + - claim + - header + type: object + type: array + extractFrom: + description: |- + ExtractFrom defines different ways to extract the JWT token from HTTP request. + If empty, it defaults to extract JWT token from the Authorization HTTP request header using Bearer schema + or access_token from query parameters. + properties: + cookies: + description: Cookies represents a list of cookie names + to extract the JWT token from. + items: + type: string + type: array + headers: + description: Headers represents a list of HTTP request + headers to extract the JWT token from. + items: + description: JWTHeaderExtractor defines an HTTP header + location to extract JWT token + properties: + name: + description: Name is the HTTP header name to retrieve + the token + type: string + valuePrefix: + description: |- + ValuePrefix is the prefix that should be stripped before extracting the token. + The format would be used by Envoy like "{ValuePrefix}". + For example, "Authorization: Bearer ", then the ValuePrefix="Bearer " with a space at the end. + type: string + required: + - name + type: object + type: array + params: + description: Params represents a list of query parameters + to extract the JWT token from. + items: + type: string + type: array + type: object + issuer: + description: |- + 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. + maxLength: 253 + type: string + localJWKS: + description: LocalJWKS defines how to get the JSON Web Key + Sets (JWKS) from a local source. + properties: + inline: + description: Inline contains the value as an inline + string. + type: string + type: + default: Inline + description: |- + Type is the type of method to use to read the body value. + Valid values are Inline and ValueRef, default is Inline. + enum: + - Inline + - ValueRef + type: string + valueRef: + description: |- + ValueRef is a reference to a local ConfigMap that contains the JSON Web Key Sets (JWKS). + + The value of key `jwks` in the ConfigMap will be used. + If the key is not found, the first value in the ConfigMap will be used. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: Exactly one of inline or valueRef must be set + with correct type. + rule: (self.type == 'Inline' && has(self.inline) && !has(self.valueRef)) + || (self.type == 'ValueRef' && !has(self.inline) && + has(self.valueRef)) + name: + description: |- + 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. + maxLength: 253 + minLength: 1 + type: string + recomputeRoute: + description: |- + RecomputeRoute clears the route cache and recalculates the routing decision. + This field must be enabled if the headers generated from the claim are used for + route matching decisions. If the recomputation selects a new route, features targeting + the new matched route will be applied. + type: boolean + remoteJWKS: + description: |- + RemoteJWKS defines how to fetch and cache JSON Web Key Sets (JWKS) from a remote + HTTP/HTTPS endpoint. + properties: + backendRef: + description: |- + BackendRef references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + + Deprecated: Use BackendRefs instead. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + backendRefs: + description: |- + BackendRefs references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + items: + description: BackendRef defines how an ObjectReference + that is specific to BackendRef. + properties: + fallback: + description: |- + Fallback indicates whether the backend is designated as a fallback. + Multiple fallback backends can be configured. + It is highly recommended to configure active or passive health checks to ensure that failover can be detected + when the active backends become unhealthy and to automatically readjust once the primary backends are healthy again. + The overprovisioning factor is set to 1.4, meaning the fallback backends will only start receiving traffic when + the health of the active backends falls below 72%. + type: boolean + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + backendSettings: + description: |- + BackendSettings holds configuration for managing the connection + to the backend. + properties: + circuitBreaker: + description: |- + Circuit Breaker settings for the upstream connections and requests. + If not set, circuit breakers will be enabled with the default thresholds + properties: + maxConnections: + default: 1024 + description: The maximum number of connections + that Envoy will establish to the referenced + backend defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRequests: + default: 1024 + description: The maximum number of parallel + requests that Envoy will make to the referenced + backend defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRetries: + default: 1024 + description: The maximum number of parallel + retries that Envoy will make to the referenced + backend defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxPendingRequests: + default: 1024 + description: The maximum number of pending requests + that Envoy will queue to the referenced backend + defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxRequestsPerConnection: + description: |- + The maximum number of requests that Envoy will make over a single connection to the referenced backend defined within a xRoute rule. + Default: unlimited. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + perEndpoint: + description: PerEndpoint defines Circuit Breakers + that will apply per-endpoint for an upstream + cluster + properties: + maxConnections: + default: 1024 + description: MaxConnections configures the + maximum number of connections that Envoy + will establish per-endpoint to the referenced + backend defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + type: object + type: object + connection: + description: Connection includes backend connection + settings. + properties: + bufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + BufferLimit Soft limit on size of the cluster’s connections read and write buffers. + BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space. + If unspecified, an implementation defined default is applied (32768 bytes). + For example, 20Mi, 1Gi, 256Ki etc. + Note: that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + socketBufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + SocketBufferLimit provides configuration for the maximum buffer size in bytes for each socket + to backend. + SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space. + For example, 20Mi, 1Gi, 256Ki etc. + Note that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + type: object + dns: + description: DNS includes dns resolution settings. + properties: + dnsRefreshRate: + description: |- + DNSRefreshRate specifies the rate at which DNS records should be refreshed. + Defaults to 30 seconds. + type: string + lookupFamily: + description: |- + LookupFamily determines how Envoy would resolve DNS for Routes where the backend is specified as a fully qualified domain name (FQDN). + If set, this configuration overrides other defaults. + enum: + - IPv4 + - IPv6 + - IPv4Preferred + - IPv6Preferred + - IPv4AndIPv6 + type: string + respectDnsTtl: + description: |- + RespectDNSTTL indicates whether the DNS Time-To-Live (TTL) should be respected. + If the value is set to true, the DNS refresh rate will be set to the resource record’s TTL. + Defaults to true. + type: boolean + type: object + healthCheck: + description: HealthCheck allows gateway to perform + active health checking on backends. + properties: + active: + description: Active health check configuration + properties: + grpc: + description: |- + GRPC defines the configuration of the GRPC health checker. + It's optional, and can only be used if the specified type is GRPC. + properties: + service: + description: |- + Service to send in the health check request. + If this is not specified, then the health check request applies to the entire + server and not to a specific service. + type: string + type: object + healthyThreshold: + default: 1 + description: HealthyThreshold defines the + number of healthy health checks required + before a backend host is marked healthy. + format: int32 + minimum: 1 + type: integer + http: + description: |- + HTTP defines the configuration of http health checker. + It's required while the health checker type is HTTP. + properties: + expectedResponse: + description: ExpectedResponse defines + a list of HTTP expected responses + to match. + properties: + binary: + description: Binary payload base64 + encoded. + format: byte + type: string + text: + description: Text payload in plain + text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type + of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, + text field needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, + binary field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + expectedStatuses: + description: |- + ExpectedStatuses defines a list of HTTP response statuses considered healthy. + Defaults to 200 only + items: + description: HTTPStatus defines the + http status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + method: + description: |- + Method defines the HTTP method used for health checking. + Defaults to GET + type: string + path: + description: Path defines the HTTP path + that will be requested during health + checking. + maxLength: 1024 + minLength: 1 + type: string + required: + - path + type: object + interval: + default: 3s + description: Interval defines the time between + active health checks. + format: duration + type: string + tcp: + description: |- + TCP defines the configuration of tcp health checker. + It's required while the health checker type is TCP. + properties: + receive: + description: Receive defines the expected + response payload. + properties: + binary: + description: Binary payload base64 + encoded. + format: byte + type: string + text: + description: Text payload in plain + text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type + of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, + text field needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, + binary field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + send: + description: Send defines the request + payload. + properties: + binary: + description: Binary payload base64 + encoded. + format: byte + type: string + text: + description: Text payload in plain + text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type + of the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, + text field needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, + binary field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + type: object + timeout: + default: 1s + description: Timeout defines the time to + wait for a health check response. + format: duration + type: string + type: + allOf: + - enum: + - HTTP + - TCP + - GRPC + - enum: + - HTTP + - TCP + - GRPC + description: Type defines the type of health + checker. + type: string + unhealthyThreshold: + default: 3 + description: UnhealthyThreshold defines + the number of unhealthy health checks + required before a backend host is marked + unhealthy. + format: int32 + minimum: 1 + type: integer + required: + - type + type: object + x-kubernetes-validations: + - message: If Health Checker type is HTTP, http + field needs to be set. + rule: 'self.type == ''HTTP'' ? has(self.http) + : !has(self.http)' + - message: If Health Checker type is TCP, tcp + field needs to be set. + rule: 'self.type == ''TCP'' ? has(self.tcp) + : !has(self.tcp)' + - message: The grpc field can only be set if + the Health Checker type is GRPC. + rule: 'has(self.grpc) ? self.type == ''GRPC'' + : true' + panicThreshold: + description: |- + When number of unhealthy endpoints for a backend reaches this threshold + Envoy will disregard health status and balance across all endpoints. + It's designed to prevent a situation in which host failures cascade throughout the cluster + as load increases. If not set, the default value is 50%. To disable panic mode, set value to `0`. + format: int32 + maximum: 100 + minimum: 0 + type: integer + passive: + description: Passive passive check configuration + properties: + baseEjectionTime: + default: 30s + description: BaseEjectionTime defines the + base duration for which a host will be + ejected on consecutive failures. + format: duration + type: string + consecutive5XxErrors: + default: 5 + description: Consecutive5xxErrors sets the + number of consecutive 5xx errors triggering + ejection. + format: int32 + type: integer + consecutiveGatewayErrors: + default: 0 + description: ConsecutiveGatewayErrors sets + the number of consecutive gateway errors + triggering ejection. + format: int32 + type: integer + consecutiveLocalOriginFailures: + default: 5 + description: |- + ConsecutiveLocalOriginFailures sets the number of consecutive local origin failures triggering ejection. + Parameter takes effect only when split_external_local_origin_errors is set to true. + format: int32 + type: integer + interval: + default: 3s + description: Interval defines the time between + passive health checks. + format: duration + type: string + maxEjectionPercent: + default: 10 + description: MaxEjectionPercent sets the + maximum percentage of hosts in a cluster + that can be ejected. + format: int32 + type: integer + splitExternalLocalOriginErrors: + default: false + description: SplitExternalLocalOriginErrors + enables splitting of errors between external + and local origin. + type: boolean + type: object + type: object + http2: + description: HTTP2 provides HTTP/2 configuration + for backend connections. + properties: + initialConnectionWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialConnectionWindowSize sets the initial window size for HTTP/2 connections. + If not set, the default value is 1 MiB. + x-kubernetes-int-or-string: true + initialStreamWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialStreamWindowSize sets the initial window size for HTTP/2 streams. + If not set, the default value is 64 KiB(64*1024). + x-kubernetes-int-or-string: true + maxConcurrentStreams: + description: |- + MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection. + If not set, the default value is 100. + format: int32 + maximum: 2147483647 + minimum: 1 + type: integer + onInvalidMessage: + description: |- + OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error + It's recommended for L2 Envoy deployments to set this value to TerminateStream. + https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two + Default: TerminateConnection + type: string + type: object + loadBalancer: + description: |- + LoadBalancer policy to apply when routing traffic from the gateway to + the backend endpoints. Defaults to `LeastRequest`. + properties: + consistentHash: + description: |- + ConsistentHash defines the configuration when the load balancer type is + set to ConsistentHash + properties: + cookie: + description: Cookie configures the cookie + hash policy when the consistent hash type + is set to Cookie. + properties: + attributes: + additionalProperties: + type: string + description: Additional Attributes to + set for the generated cookie. + type: object + name: + description: |- + Name of the cookie to hash. + If this cookie does not exist in the request, Envoy will generate a cookie and set + the TTL on the response back to the client based on Layer 4 + attributes of the backend endpoint, to ensure that these future requests + go to the same backend endpoint. Make sure to set the TTL field for this case. + type: string + ttl: + description: |- + TTL of the generated cookie if the cookie is not present. This value sets the + Max-Age attribute value. + type: string + required: + - name + type: object + header: + description: Header configures the header + hash policy when the consistent hash type + is set to Header. + properties: + name: + description: Name of the header to hash. + type: string + required: + - name + type: object + tableSize: + default: 65537 + description: The table size for consistent + hashing, must be prime number limited + to 5000011. + format: int64 + maximum: 5000011 + minimum: 2 + type: integer + type: + description: |- + ConsistentHashType defines the type of input to hash on. Valid Type values are + "SourceIP", + "Header", + "Cookie". + enum: + - SourceIP + - Header + - Cookie + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If consistent hash type is header, + the header field must be set. + rule: 'self.type == ''Header'' ? has(self.header) + : !has(self.header)' + - message: If consistent hash type is cookie, + the cookie field must be set. + rule: 'self.type == ''Cookie'' ? has(self.cookie) + : !has(self.cookie)' + slowStart: + description: |- + SlowStart defines the configuration related to the slow start load balancer policy. + If set, during slow start window, traffic sent to the newly added hosts will gradually increase. + Currently this is only supported for RoundRobin and LeastRequest load balancers + properties: + window: + description: |- + Window defines the duration of the warm up period for newly added host. + During slow start window, traffic sent to the newly added hosts will gradually increase. + Currently only supports linear growth of traffic. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#config-cluster-v3-cluster-slowstartconfig + type: string + required: + - window + type: object + type: + description: |- + Type decides the type of Load Balancer policy. + Valid LoadBalancerType values are + "ConsistentHash", + "LeastRequest", + "Random", + "RoundRobin". + enum: + - ConsistentHash + - LeastRequest + - Random + - RoundRobin + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If LoadBalancer type is consistentHash, + consistentHash field needs to be set. + rule: 'self.type == ''ConsistentHash'' ? has(self.consistentHash) + : !has(self.consistentHash)' + - message: Currently SlowStart is only supported + for RoundRobin and LeastRequest load balancers. + rule: 'self.type in [''Random'', ''ConsistentHash''] + ? !has(self.slowStart) : true ' + proxyProtocol: + description: ProxyProtocol enables the Proxy Protocol + when communicating with the backend. + properties: + version: + description: |- + Version of ProxyProtol + Valid ProxyProtocolVersion values are + "V1" + "V2" + enum: + - V1 + - V2 + type: string + required: + - version + type: object + retry: + description: |- + Retry provides more advanced usage, allowing users to customize the number of retries, retry fallback strategy, and retry triggering conditions. + If not set, retry will be disabled. + properties: + numRetries: + default: 2 + description: NumRetries is the number of retries + to be attempted. Defaults to 2. + format: int32 + minimum: 0 + type: integer + perRetry: + description: PerRetry is the retry policy to + be applied per retry attempt. + properties: + backOff: + description: |- + Backoff is the backoff policy to be applied per retry attempt. gateway uses a fully jittered exponential + back-off algorithm for retries. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#config-http-filters-router-x-envoy-max-retries + properties: + baseInterval: + description: BaseInterval is the base + interval between retries. + format: duration + type: string + maxInterval: + description: |- + MaxInterval is the maximum interval between retries. This parameter is optional, but must be greater than or equal to the base_interval if set. + The default is 10 times the base_interval + format: duration + type: string + type: object + timeout: + description: Timeout is the timeout per + retry attempt. + format: duration + type: string + type: object + retryOn: + description: |- + RetryOn specifies the retry trigger condition. + + If not specified, the default is to retry on connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes(503). + properties: + httpStatusCodes: + description: |- + HttpStatusCodes specifies the http status codes to be retried. + The retriable-status-codes trigger must also be configured for these status codes to trigger a retry. + items: + description: HTTPStatus defines the http + status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + triggers: + description: Triggers specifies the retry + trigger condition(Http/Grpc). + items: + description: TriggerEnum specifies the + conditions that trigger retries. + enum: + - 5xx + - gateway-error + - reset + - connect-failure + - retriable-4xx + - refused-stream + - retriable-status-codes + - cancelled + - deadline-exceeded + - internal + - resource-exhausted + - unavailable + type: string + type: array + type: object + type: object + tcpKeepalive: + description: |- + TcpKeepalive settings associated with the upstream client connection. + Disabled by default. + properties: + idleTime: + description: |- + The duration a connection needs to be idle before keep-alive + probes start being sent. + The duration format is + Defaults to `7200s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + The duration between keep-alive probes. + Defaults to `75s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + probes: + description: |- + The total number of unacknowledged probes to send before deciding + the connection is dead. + Defaults to 9. + format: int32 + type: integer + type: object + timeout: + description: Timeout settings for the backend connections. + properties: + http: + description: Timeout settings for HTTP. + properties: + connectionIdleTimeout: + description: |- + The idle timeout for an HTTP connection. Idle time is defined as a period in which there are no active requests in the connection. + Default: 1 hour. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + maxConnectionDuration: + description: |- + The maximum duration of an HTTP connection. + Default: unlimited. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + requestTimeout: + description: RequestTimeout is the time + until which entire response is received + from the upstream. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + tcp: + description: Timeout settings for TCP. + properties: + connectTimeout: + description: |- + The timeout for network connection establishment, including TCP and TLS handshakes. + Default: 10 seconds. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + type: object + type: object + uri: + description: |- + URI is the HTTPS URI to fetch the JWKS. Envoy's system trust bundle is used to validate the server certificate. + If a custom trust bundle is needed, it can be specified in a BackendTLSConfig resource and target the BackendRefs. + maxLength: 253 + minLength: 1 + type: string + required: + - uri + type: object + x-kubernetes-validations: + - message: BackendRefs must be used, backendRef is not supported. + rule: '!has(self.backendRef)' + - message: Retry timeout is not supported. + rule: has(self.backendSettings)? (has(self.backendSettings.retry)?(has(self.backendSettings.retry.perRetry)? + !has(self.backendSettings.retry.perRetry.timeout):true):true):true + - message: HTTPStatusCodes is not supported. + rule: has(self.backendSettings)? (has(self.backendSettings.retry)?(has(self.backendSettings.retry.retryOn)? + !has(self.backendSettings.retry.retryOn.httpStatusCodes):true):true):true + required: + - name + type: object + x-kubernetes-validations: + - message: claimToHeaders must be specified if recomputeRoute + is enabled. + rule: '(has(self.recomputeRoute) && self.recomputeRoute) ? + size(self.claimToHeaders) > 0 : true' + - message: either remoteJWKS or localJWKS must be specified. + rule: has(self.remoteJWKS) || has(self.localJWKS) + - message: remoteJWKS and localJWKS cannot both be specified. + rule: '!(has(self.remoteJWKS) && has(self.localJWKS))' + maxItems: 4 + minItems: 1 + type: array + required: + - providers + type: object + oidc: + description: OIDC defines the configuration for the OpenID Connect + (OIDC) authentication. + properties: + clientID: + description: |- + The client ID to be used in the OIDC + [Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest). + minLength: 1 + type: string + clientSecret: + description: |- + The Kubernetes secret which contains the OIDC client secret to be used in the + [Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest). + + This is an Opaque secret. The client secret should be stored in the key + "client-secret". + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + cookieDomain: + description: |- + The optional domain to set the access and ID token cookies on. + If not set, the cookies will default to the host of the request, not including the subdomains. + If set, the cookies will be set on the specified domain and all subdomains. + This means that requests to any subdomain will not require reauthentication after users log in to the parent domain. + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9]))*$ + type: string + cookieNames: + description: |- + The optional cookie name overrides to be used for Bearer and IdToken cookies in the + [Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest). + If not specified, uses a randomly generated suffix + properties: + accessToken: + description: |- + The name of the cookie used to store the AccessToken in the + [Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest). + If not specified, defaults to "AccessToken-(randomly generated uid)" + type: string + idToken: + description: |- + The name of the cookie used to store the IdToken in the + [Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest). + If not specified, defaults to "IdToken-(randomly generated uid)" + type: string + type: object + defaultRefreshTokenTTL: + description: |- + DefaultRefreshTokenTTL is the default lifetime of the refresh token. + This field is only used when the exp (expiration time) claim is omitted in + the refresh token or the refresh token is not JWT. + + If not specified, defaults to 604800s (one week). + Note: this field is only applicable when the "refreshToken" field is set to true. + type: string + defaultTokenTTL: + description: |- + DefaultTokenTTL is the default lifetime of the id token and access token. + Please note that Envoy will always use the expiry time from the response + of the authorization server if it is provided. This field is only used when + the expiry time is not provided by the authorization. + + If not specified, defaults to 0. In this case, the "expires_in" field in + the authorization response must be set by the authorization server, or the + OAuth flow will fail. + type: string + forwardAccessToken: + description: |- + ForwardAccessToken indicates whether the Envoy should forward the access token + via the Authorization header Bearer scheme to the upstream. + If not specified, defaults to false. + type: boolean + logoutPath: + description: |- + The path to log a user out, clearing their credential cookies. + + If not specified, uses a default logout path "/logout" + type: string + provider: + description: The OIDC Provider configuration. + properties: + authorizationEndpoint: + description: |- + The OIDC Provider's [authorization endpoint](https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint). + If not provided, EG will try to discover it from the provider's [Well-Known Configuration Endpoint](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse). + type: string + backendRef: + description: |- + BackendRef references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + + Deprecated: Use BackendRefs instead. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + backendRefs: + description: |- + BackendRefs references a Kubernetes object that represents the + backend server to which the authorization request will be sent. + items: + description: BackendRef defines how an ObjectReference that + is specific to BackendRef. + properties: + fallback: + description: |- + Fallback indicates whether the backend is designated as a fallback. + Multiple fallback backends can be configured. + It is highly recommended to configure active or passive health checks to ensure that failover can be detected + when the active backends become unhealthy and to automatically readjust once the primary backends are healthy again. + The overprovisioning factor is set to 1.4, meaning the fallback backends will only start receiving traffic when + the health of the active backends falls below 72%. + type: boolean + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + backendSettings: + description: |- + BackendSettings holds configuration for managing the connection + to the backend. + properties: + circuitBreaker: + description: |- + Circuit Breaker settings for the upstream connections and requests. + If not set, circuit breakers will be enabled with the default thresholds + properties: + maxConnections: + default: 1024 + description: The maximum number of connections that + Envoy will establish to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRequests: + default: 1024 + description: The maximum number of parallel requests + that Envoy will make to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxParallelRetries: + default: 1024 + description: The maximum number of parallel retries + that Envoy will make to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxPendingRequests: + default: 1024 + description: The maximum number of pending requests + that Envoy will queue to the referenced backend + defined within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + maxRequestsPerConnection: + description: |- + The maximum number of requests that Envoy will make over a single connection to the referenced backend defined within a xRoute rule. + Default: unlimited. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + perEndpoint: + description: PerEndpoint defines Circuit Breakers + that will apply per-endpoint for an upstream cluster + properties: + maxConnections: + default: 1024 + description: MaxConnections configures the maximum + number of connections that Envoy will establish + per-endpoint to the referenced backend defined + within a xRoute rule. + format: int64 + maximum: 4294967295 + minimum: 0 + type: integer + type: object + type: object + connection: + description: Connection includes backend connection settings. + properties: + bufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + BufferLimit Soft limit on size of the cluster’s connections read and write buffers. + BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space. + If unspecified, an implementation defined default is applied (32768 bytes). + For example, 20Mi, 1Gi, 256Ki etc. + Note: that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + socketBufferLimit: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + SocketBufferLimit provides configuration for the maximum buffer size in bytes for each socket + to backend. + SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space. + For example, 20Mi, 1Gi, 256Ki etc. + Note that when the suffix is not provided, the value is interpreted as bytes. + x-kubernetes-int-or-string: true + type: object + dns: + description: DNS includes dns resolution settings. + properties: + dnsRefreshRate: + description: |- + DNSRefreshRate specifies the rate at which DNS records should be refreshed. + Defaults to 30 seconds. + type: string + lookupFamily: + description: |- + LookupFamily determines how Envoy would resolve DNS for Routes where the backend is specified as a fully qualified domain name (FQDN). + If set, this configuration overrides other defaults. + enum: + - IPv4 + - IPv6 + - IPv4Preferred + - IPv6Preferred + - IPv4AndIPv6 + type: string + respectDnsTtl: + description: |- + RespectDNSTTL indicates whether the DNS Time-To-Live (TTL) should be respected. + If the value is set to true, the DNS refresh rate will be set to the resource record’s TTL. + Defaults to true. + type: boolean + type: object + healthCheck: + description: HealthCheck allows gateway to perform active + health checking on backends. + properties: + active: + description: Active health check configuration + properties: + grpc: + description: |- + GRPC defines the configuration of the GRPC health checker. + It's optional, and can only be used if the specified type is GRPC. + properties: + service: + description: |- + Service to send in the health check request. + If this is not specified, then the health check request applies to the entire + server and not to a specific service. + type: string + type: object + healthyThreshold: + default: 1 + description: HealthyThreshold defines the number + of healthy health checks required before a backend + host is marked healthy. + format: int32 + minimum: 1 + type: integer + http: + description: |- + HTTP defines the configuration of http health checker. + It's required while the health checker type is HTTP. + properties: + expectedResponse: + description: ExpectedResponse defines a list + of HTTP expected responses to match. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of + the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, binary + field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + expectedStatuses: + description: |- + ExpectedStatuses defines a list of HTTP response statuses considered healthy. + Defaults to 200 only + items: + description: HTTPStatus defines the http + status code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + method: + description: |- + Method defines the HTTP method used for health checking. + Defaults to GET + type: string + path: + description: Path defines the HTTP path that + will be requested during health checking. + maxLength: 1024 + minLength: 1 + type: string + required: + - path + type: object + interval: + default: 3s + description: Interval defines the time between + active health checks. + format: duration + type: string + tcp: + description: |- + TCP defines the configuration of tcp health checker. + It's required while the health checker type is TCP. + properties: + receive: + description: Receive defines the expected + response payload. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of + the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, binary + field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + send: + description: Send defines the request payload. + properties: + binary: + description: Binary payload base64 encoded. + format: byte + type: string + text: + description: Text payload in plain text. + type: string + type: + allOf: + - enum: + - Text + - Binary + - enum: + - Text + - Binary + description: Type defines the type of + the payload. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If payload type is Text, text field + needs to be set. + rule: 'self.type == ''Text'' ? has(self.text) + : !has(self.text)' + - message: If payload type is Binary, binary + field needs to be set. + rule: 'self.type == ''Binary'' ? has(self.binary) + : !has(self.binary)' + type: object + timeout: + default: 1s + description: Timeout defines the time to wait + for a health check response. + format: duration + type: string + type: + allOf: + - enum: + - HTTP + - TCP + - GRPC + - enum: + - HTTP + - TCP + - GRPC + description: Type defines the type of health checker. + type: string + unhealthyThreshold: + default: 3 + description: UnhealthyThreshold defines the number + of unhealthy health checks required before a + backend host is marked unhealthy. + format: int32 + minimum: 1 + type: integer + required: + - type + type: object + x-kubernetes-validations: + - message: If Health Checker type is HTTP, http field + needs to be set. + rule: 'self.type == ''HTTP'' ? has(self.http) : + !has(self.http)' + - message: If Health Checker type is TCP, tcp field + needs to be set. + rule: 'self.type == ''TCP'' ? has(self.tcp) : !has(self.tcp)' + - message: The grpc field can only be set if the Health + Checker type is GRPC. + rule: 'has(self.grpc) ? self.type == ''GRPC'' : + true' + panicThreshold: + description: |- + When number of unhealthy endpoints for a backend reaches this threshold + Envoy will disregard health status and balance across all endpoints. + It's designed to prevent a situation in which host failures cascade throughout the cluster + as load increases. If not set, the default value is 50%. To disable panic mode, set value to `0`. + format: int32 + maximum: 100 + minimum: 0 + type: integer + passive: + description: Passive passive check configuration + properties: + baseEjectionTime: + default: 30s + description: BaseEjectionTime defines the base + duration for which a host will be ejected on + consecutive failures. + format: duration + type: string + consecutive5XxErrors: + default: 5 + description: Consecutive5xxErrors sets the number + of consecutive 5xx errors triggering ejection. + format: int32 + type: integer + consecutiveGatewayErrors: + default: 0 + description: ConsecutiveGatewayErrors sets the + number of consecutive gateway errors triggering + ejection. + format: int32 + type: integer + consecutiveLocalOriginFailures: + default: 5 + description: |- + ConsecutiveLocalOriginFailures sets the number of consecutive local origin failures triggering ejection. + Parameter takes effect only when split_external_local_origin_errors is set to true. + format: int32 + type: integer + interval: + default: 3s + description: Interval defines the time between + passive health checks. + format: duration + type: string + maxEjectionPercent: + default: 10 + description: MaxEjectionPercent sets the maximum + percentage of hosts in a cluster that can be + ejected. + format: int32 + type: integer + splitExternalLocalOriginErrors: + default: false + description: SplitExternalLocalOriginErrors enables + splitting of errors between external and local + origin. + type: boolean + type: object + type: object + http2: + description: HTTP2 provides HTTP/2 configuration for backend + connections. + properties: + initialConnectionWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialConnectionWindowSize sets the initial window size for HTTP/2 connections. + If not set, the default value is 1 MiB. + x-kubernetes-int-or-string: true + initialStreamWindowSize: + allOf: + - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + - pattern: ^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$ + anyOf: + - type: integer + - type: string + description: |- + InitialStreamWindowSize sets the initial window size for HTTP/2 streams. + If not set, the default value is 64 KiB(64*1024). + x-kubernetes-int-or-string: true + maxConcurrentStreams: + description: |- + MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection. + If not set, the default value is 100. + format: int32 + maximum: 2147483647 + minimum: 1 + type: integer + onInvalidMessage: + description: |- + OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error + It's recommended for L2 Envoy deployments to set this value to TerminateStream. + https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two + Default: TerminateConnection + type: string + type: object + loadBalancer: + description: |- + LoadBalancer policy to apply when routing traffic from the gateway to + the backend endpoints. Defaults to `LeastRequest`. + properties: + consistentHash: + description: |- + ConsistentHash defines the configuration when the load balancer type is + set to ConsistentHash + properties: + cookie: + description: Cookie configures the cookie hash + policy when the consistent hash type is set + to Cookie. + properties: + attributes: + additionalProperties: + type: string + description: Additional Attributes to set + for the generated cookie. + type: object + name: + description: |- + Name of the cookie to hash. + If this cookie does not exist in the request, Envoy will generate a cookie and set + the TTL on the response back to the client based on Layer 4 + attributes of the backend endpoint, to ensure that these future requests + go to the same backend endpoint. Make sure to set the TTL field for this case. + type: string + ttl: + description: |- + TTL of the generated cookie if the cookie is not present. This value sets the + Max-Age attribute value. + type: string + required: + - name + type: object + header: + description: Header configures the header hash + policy when the consistent hash type is set + to Header. + properties: + name: + description: Name of the header to hash. + type: string + required: + - name + type: object + tableSize: + default: 65537 + description: The table size for consistent hashing, + must be prime number limited to 5000011. + format: int64 + maximum: 5000011 + minimum: 2 + type: integer + type: + description: |- + ConsistentHashType defines the type of input to hash on. Valid Type values are + "SourceIP", + "Header", + "Cookie". + enum: + - SourceIP + - Header + - Cookie + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If consistent hash type is header, the + header field must be set. + rule: 'self.type == ''Header'' ? has(self.header) + : !has(self.header)' + - message: If consistent hash type is cookie, the + cookie field must be set. + rule: 'self.type == ''Cookie'' ? has(self.cookie) + : !has(self.cookie)' + slowStart: + description: |- + SlowStart defines the configuration related to the slow start load balancer policy. + If set, during slow start window, traffic sent to the newly added hosts will gradually increase. + Currently this is only supported for RoundRobin and LeastRequest load balancers + properties: + window: + description: |- + Window defines the duration of the warm up period for newly added host. + During slow start window, traffic sent to the newly added hosts will gradually increase. + Currently only supports linear growth of traffic. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#config-cluster-v3-cluster-slowstartconfig + type: string + required: + - window + type: object + type: + description: |- + Type decides the type of Load Balancer policy. + Valid LoadBalancerType values are + "ConsistentHash", + "LeastRequest", + "Random", + "RoundRobin". + enum: + - ConsistentHash + - LeastRequest + - Random + - RoundRobin + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: If LoadBalancer type is consistentHash, consistentHash + field needs to be set. + rule: 'self.type == ''ConsistentHash'' ? has(self.consistentHash) + : !has(self.consistentHash)' + - message: Currently SlowStart is only supported for RoundRobin + and LeastRequest load balancers. + rule: 'self.type in [''Random'', ''ConsistentHash''] + ? !has(self.slowStart) : true ' + proxyProtocol: + description: ProxyProtocol enables the Proxy Protocol + when communicating with the backend. + properties: + version: + description: |- + Version of ProxyProtol + Valid ProxyProtocolVersion values are + "V1" + "V2" + enum: + - V1 + - V2 + type: string + required: + - version + type: object + retry: + description: |- + Retry provides more advanced usage, allowing users to customize the number of retries, retry fallback strategy, and retry triggering conditions. + If not set, retry will be disabled. + properties: + numRetries: + default: 2 + description: NumRetries is the number of retries to + be attempted. Defaults to 2. + format: int32 + minimum: 0 + type: integer + perRetry: + description: PerRetry is the retry policy to be applied + per retry attempt. + properties: + backOff: + description: |- + Backoff is the backoff policy to be applied per retry attempt. gateway uses a fully jittered exponential + back-off algorithm for retries. For additional details, + see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/router_filter#config-http-filters-router-x-envoy-max-retries + properties: + baseInterval: + description: BaseInterval is the base interval + between retries. + format: duration + type: string + maxInterval: + description: |- + MaxInterval is the maximum interval between retries. This parameter is optional, but must be greater than or equal to the base_interval if set. + The default is 10 times the base_interval + format: duration + type: string + type: object + timeout: + description: Timeout is the timeout per retry + attempt. + format: duration + type: string + type: object + retryOn: + description: |- + RetryOn specifies the retry trigger condition. + + If not specified, the default is to retry on connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes(503). + properties: + httpStatusCodes: + description: |- + HttpStatusCodes specifies the http status codes to be retried. + The retriable-status-codes trigger must also be configured for these status codes to trigger a retry. + items: + description: HTTPStatus defines the http status + code. + exclusiveMaximum: true + maximum: 600 + minimum: 100 + type: integer + type: array + triggers: + description: Triggers specifies the retry trigger + condition(Http/Grpc). + items: + description: TriggerEnum specifies the conditions + that trigger retries. + enum: + - 5xx + - gateway-error + - reset + - connect-failure + - retriable-4xx + - refused-stream + - retriable-status-codes + - cancelled + - deadline-exceeded + - internal + - resource-exhausted + - unavailable + type: string + type: array + type: object + type: object + tcpKeepalive: + description: |- + TcpKeepalive settings associated with the upstream client connection. + Disabled by default. + properties: + idleTime: + description: |- + The duration a connection needs to be idle before keep-alive + probes start being sent. + The duration format is + Defaults to `7200s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + interval: + description: |- + The duration between keep-alive probes. + Defaults to `75s`. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + probes: + description: |- + The total number of unacknowledged probes to send before deciding + the connection is dead. + Defaults to 9. + format: int32 + type: integer + type: object + timeout: + description: Timeout settings for the backend connections. + properties: + http: + description: Timeout settings for HTTP. + properties: + connectionIdleTimeout: + description: |- + The idle timeout for an HTTP connection. Idle time is defined as a period in which there are no active requests in the connection. + Default: 1 hour. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + maxConnectionDuration: + description: |- + The maximum duration of an HTTP connection. + Default: unlimited. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + requestTimeout: + description: RequestTimeout is the time until + which entire response is received from the upstream. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + tcp: + description: Timeout settings for TCP. + properties: + connectTimeout: + description: |- + The timeout for network connection establishment, including TCP and TLS handshakes. + Default: 10 seconds. + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + type: object + type: object + issuer: + description: |- + The OIDC Provider's [issuer identifier](https://openid.net/specs/openid-connect-discovery-1_0.html#IssuerDiscovery). + Issuer MUST be a URI RFC 3986 [RFC3986] with a scheme component that MUST + be https, a host component, and optionally, port and path components and + no query or fragment components. + minLength: 1 + type: string + tokenEndpoint: + description: |- + The OIDC Provider's [token endpoint](https://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint). + If not provided, EG will try to discover it from the provider's [Well-Known Configuration Endpoint](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse). + type: string + required: + - issuer + type: object + x-kubernetes-validations: + - message: BackendRefs must be used, backendRef is not supported. + rule: '!has(self.backendRef)' + - message: Retry timeout is not supported. + rule: has(self.backendSettings)? (has(self.backendSettings.retry)?(has(self.backendSettings.retry.perRetry)? + !has(self.backendSettings.retry.perRetry.timeout):true):true):true + - message: HTTPStatusCodes is not supported. + rule: has(self.backendSettings)? (has(self.backendSettings.retry)?(has(self.backendSettings.retry.retryOn)? + !has(self.backendSettings.retry.retryOn.httpStatusCodes):true):true):true + redirectURL: + description: |- + The redirect URL to be used in the OIDC + [Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest). + If not specified, uses the default redirect URI "%REQ(x-forwarded-proto)%://%REQ(:authority)%/oauth2/callback" + type: string + refreshToken: + description: |- + RefreshToken indicates whether the Envoy should automatically refresh the + id token and access token when they expire. + When set to true, the Envoy will use the refresh token to get a new id token + and access token when they expire. + + If not specified, defaults to false. + type: boolean + resources: + description: |- + The OIDC resources to be used in the + [Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest). + items: + type: string + type: array + scopes: + description: |- + The OIDC scopes to be used in the + [Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest). + The "openid" scope is always added to the list of scopes if not already + specified. + items: + type: string + type: array + required: + - clientID + - clientSecret + - provider + type: object + targetRef: + description: |- + TargetRef is the name of the resource this policy is being attached to. + This policy and the TargetRef MUST be in the same namespace for this + Policy to have effect + + Deprecated: use targetRefs/targetSelectors instead + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + targetRefs: + description: |- + TargetRefs are the names of the Gateway resources this policy + is being attached to. + items: + description: |- + LocalPolicyTargetReferenceWithSectionName identifies an API object to apply a + direct policy to. This should be used as part of Policy resources that can + target single resources. For more information on how this policy attachment + mode works, and a sample Policy resource, refer to the policy attachment + documentation for Gateway API. + + Note: This should only be used for direct policy attachment when references + to SectionName are actually needed. In all other cases, + LocalPolicyTargetReference should be used. + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + type: array + targetSelectors: + description: TargetSelectors allow targeting resources for this policy + based on labels + items: + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group that this selector targets. + Defaults to gateway.networking.k8s.io + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the resource kind that this selector targets. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: MatchLabels are the set of label selectors for + identifying the targeted resource + type: object + required: + - kind + type: object + x-kubernetes-validations: + - message: group must be gateway.networking.k8s.io + rule: 'has(self.group) ? self.group == ''gateway.networking.k8s.io'' + : true ' + type: array + type: object + x-kubernetes-validations: + - message: either targetRef or targetRefs must be used + rule: '(has(self.targetRef) && !has(self.targetRefs)) || (!has(self.targetRef) + && has(self.targetRefs)) || (has(self.targetSelectors) && self.targetSelectors.size() + > 0) ' + - message: this policy can only have a targetRef.group of gateway.networking.k8s.io + rule: 'has(self.targetRef) ? self.targetRef.group == ''gateway.networking.k8s.io'' + : true' + - message: this policy can only have a targetRef.kind of Gateway/HTTPRoute/GRPCRoute + rule: 'has(self.targetRef) ? self.targetRef.kind in [''Gateway'', ''HTTPRoute'', + ''GRPCRoute''] : true' + - message: this policy does not yet support the sectionName field + rule: 'has(self.targetRef) ? !has(self.targetRef.sectionName) : true' + - message: this policy can only have a targetRefs[*].group of gateway.networking.k8s.io + rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, ref.group == + ''gateway.networking.k8s.io'') : true ' + - message: this policy can only have a targetRefs[*].kind of Gateway/HTTPRoute/GRPCRoute + rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, ref.kind in [''Gateway'', + ''HTTPRoute'', ''GRPCRoute'']) : true ' + - message: this policy does not yet support the sectionName field + rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, !has(ref.sectionName)) + : true' + - message: if authorization.rules.principal.jwt is used, jwt must be defined + rule: '(has(self.authorization) && has(self.authorization.rules) && + self.authorization.rules.exists(r, has(r.principal.jwt))) ? has(self.jwt) + : true' + status: + description: Status defines the current status of SecurityPolicy. + properties: + ancestors: + description: |- + Ancestors is a list of ancestor resources (usually Gateways) that are + associated with the policy, and the status of the policy with respect to + each ancestor. When this policy attaches to a parent, the controller that + manages the parent and the ancestors MUST add an entry to this list when + the controller first sees the policy and SHOULD update the entry as + appropriate when the relevant ancestor is modified. + + Note that choosing the relevant ancestor is left to the Policy designers; + an important part of Policy design is designing the right object level at + which to namespace this status. + + Note also that implementations MUST ONLY populate ancestor status for + the Ancestor resources they are responsible for. Implementations MUST + use the ControllerName field to uniquely identify the entries in this list + that they are responsible for. + + Note that to achieve this, the list of PolicyAncestorStatus structs + MUST be treated as a map with a composite key, made up of the AncestorRef + and ControllerName fields combined. + + A maximum of 16 ancestors will be represented in this list. An empty list + means the Policy is not relevant for any ancestors. + + If this slice is full, implementations MUST NOT add further entries. + Instead they MUST consider the policy unimplementable and signal that + on any related resources such as the ancestor that would be referenced + here. For example, if this list was full on BackendTLSPolicy, no + additional Gateways would be able to reference the Service targeted by + the BackendTLSPolicy. + items: + description: |- + PolicyAncestorStatus describes the status of a route with respect to an + associated Ancestor. + + Ancestors refer to objects that are either the Target of a policy or above it + in terms of object hierarchy. For example, if a policy targets a Service, the + Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and + the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most + useful object to place Policy status on, so we recommend that implementations + SHOULD use Gateway as the PolicyAncestorStatus object unless the designers + have a _very_ good reason otherwise. + + In the context of policy attachment, the Ancestor is used to distinguish which + resource results in a distinct application of this policy. For example, if a policy + targets a Service, it may have a distinct result per attached Gateway. + + Policies targeting the same resource may have different effects depending on the + ancestors of those resources. For example, different Gateways targeting the same + Service may have different capabilities, especially if they have different underlying + implementations. + + For example, in BackendTLSPolicy, the Policy attaches to a Service that is + used as a backend in a HTTPRoute that is itself attached to a Gateway. + In this case, the relevant object for status is the Gateway, and that is the + ancestor object referred to in this status. + + Note that a parent is also an ancestor, so for objects where the parent is the + relevant object for status, this struct SHOULD still be used. + + This struct is intended to be used in a slice that's effectively a map, + with a composite key made up of the AncestorRef and the ControllerName. + properties: + ancestorRef: + description: |- + AncestorRef corresponds with a ParentRef in the spec that this + PolicyAncestorStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: Conditions describes the status of the Policy with + respect to the given Ancestor. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - controllerName + type: object + maxItems: 16 + type: array + required: + - ancestors + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/test/helm/gateway-crds-helm/gateway-api-crds.in.yaml b/test/helm/gateway-crds-helm/gateway-api-crds.in.yaml new file mode 100644 index 0000000000..0ce81426af --- /dev/null +++ b/test/helm/gateway-crds-helm/gateway-api-crds.in.yaml @@ -0,0 +1,5 @@ +crds: + gatewayAPI: + enabled: true + envoyGateway: + enabled: false diff --git a/test/helm/gateway-crds-helm/gateway-api-crds.out.yaml b/test/helm/gateway-crds-helm/gateway-api-crds.out.yaml new file mode 100644 index 0000000000..c606e60a0b --- /dev/null +++ b/test/helm/gateway-crds-helm/gateway-api-crds.out.yaml @@ -0,0 +1,17331 @@ +--- +# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_backendtlspolicies.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + labels: + gateway.networking.k8s.io/policy: Direct + name: backendtlspolicies.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: BackendTLSPolicy + listKind: BackendTLSPolicyList + plural: backendtlspolicies + shortNames: + - btlspolicy + singular: backendtlspolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha3 + schema: + openAPIV3Schema: + description: |- + BackendTLSPolicy provides a way to configure how a Gateway + connects to a Backend via TLS. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of BackendTLSPolicy. + properties: + options: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Options are a list of key/value pairs to enable extended TLS + configuration for each implementation. For example, configuring the + minimum TLS version or supported cipher suites. + + A set of common keys MAY be defined by the API in the future. To avoid + any ambiguity, implementation-specific definitions MUST use + domain-prefixed names, such as `example.com/my-custom-option`. + Un-prefixed names are reserved for key names defined by Gateway API. + + Support: Implementation-specific + maxProperties: 16 + type: object + targetRefs: + description: |- + TargetRefs identifies an API object to apply the policy to. + Only Services have Extended support. Implementations MAY support + additional objects, with Implementation Specific support. + Note that this config applies to the entire referenced resource + by default, but this default may change in the future to provide + a more granular application of the policy. + + TargetRefs must be _distinct_. This means either that: + + * They select different targets. If this is the case, then targetRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, and `name` must + be unique across all targetRef entries in the BackendTLSPolicy. + * They select different sectionNames in the same target. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + items: + description: |- + LocalPolicyTargetReferenceWithSectionName identifies an API object to apply a + direct policy to. This should be used as part of Policy resources that can + target single resources. For more information on how this policy attachment + mode works, and a sample Policy resource, refer to the policy attachment + documentation for Gateway API. + + Note: This should only be used for direct policy attachment when references + to SectionName are actually needed. In all other cases, + LocalPolicyTargetReference should be used. + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-validations: + - message: sectionName must be specified when targetRefs includes + 2 or more references to the same target + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name ? ((!has(p1.sectionName) || p1.sectionName + == '''') == (!has(p2.sectionName) || p2.sectionName == '''')) + : true))' + - message: sectionName must be unique when targetRefs includes 2 or + more references to the same target + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.sectionName) || + p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || (has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)))) + validation: + description: Validation contains backend TLS validation configuration. + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to Kubernetes objects that + contain a PEM-encoded TLS CA certificate bundle, which is used to + validate a TLS handshake between the Gateway and backend Pod. + + If CACertificateRefs is empty or unspecified, then WellKnownCACertificates must be + specified. Only one of CACertificateRefs or WellKnownCACertificates may be specified, + not both. If CACertificateRefs is empty or unspecified, the configuration for + WellKnownCACertificates MUST be honored instead if supported by the implementation. + + References to a resource in a different namespace are invalid for the + moment, although we will revisit this in the future. + + A single CACertificateRef to a Kubernetes ConfigMap kind has "Core" support. + Implementations MAY choose to support attaching multiple certificates to + a backend, but this behavior is implementation-specific. + + Support: Core - An optional single reference to a Kubernetes ConfigMap, + with the CA certificate in a key named `ca.crt`. + + Support: Implementation-specific (More than one reference, or other kinds + of resources). + items: + description: |- + LocalObjectReference identifies an API object within the namespace of the + referrer. + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" + or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + maxItems: 8 + type: array + hostname: + description: |- + Hostname is used for two purposes in the connection between Gateways and + backends: + + 1. Hostname MUST be used as the SNI to connect to the backend (RFC 6066). + 2. Hostname MUST be used for authentication and MUST match the certificate served by the matching backend, unless SubjectAltNames is specified. + authentication and MUST match the certificate served by the matching + backend. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + subjectAltNames: + description: |- + SubjectAltNames contains one or more Subject Alternative Names. + When specified the certificate served from the backend MUST + have at least one Subject Alternate Name matching one of the specified SubjectAltNames. + + Support: Extended + items: + description: SubjectAltName represents Subject Alternative Name. + properties: + hostname: + description: |- + Hostname contains Subject Alternative Name specified in DNS name format. + Required when Type is set to Hostname, ignored otherwise. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: + description: |- + Type determines the format of the Subject Alternative Name. Always required. + + Support: Core + enum: + - Hostname + - URI + type: string + uri: + description: |- + URI contains Subject Alternative Name specified in a full URI format. + It MUST include both a scheme (e.g., "http" or "ftp") and a scheme-specific-part. + Common values include SPIFFE IDs like "spiffe://mycluster.example.com/ns/myns/sa/svc1sa". + Required when Type is set to URI, ignored otherwise. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))? + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: SubjectAltName element must contain Hostname, if + Type is set to Hostname + rule: '!(self.type == "Hostname" && (!has(self.hostname) || + self.hostname == ""))' + - message: SubjectAltName element must not contain Hostname, + if Type is not set to Hostname + rule: '!(self.type != "Hostname" && has(self.hostname) && + self.hostname != "")' + - message: SubjectAltName element must contain URI, if Type + is set to URI + rule: '!(self.type == "URI" && (!has(self.uri) || self.uri + == ""))' + - message: SubjectAltName element must not contain URI, if Type + is not set to URI + rule: '!(self.type != "URI" && has(self.uri) && self.uri != + "")' + maxItems: 5 + type: array + wellKnownCACertificates: + description: |- + WellKnownCACertificates specifies whether system CA certificates may be used in + the TLS handshake between the gateway and backend pod. + + If WellKnownCACertificates is unspecified or empty (""), then CACertificateRefs + must be specified with at least one entry for a valid configuration. Only one of + CACertificateRefs or WellKnownCACertificates may be specified, not both. If an + implementation does not support the WellKnownCACertificates field or the value + supplied is not supported, the Status Conditions on the Policy MUST be + updated to include an Accepted: False Condition with Reason: Invalid. + + Support: Implementation-specific + enum: + - System + type: string + required: + - hostname + type: object + x-kubernetes-validations: + - message: must not contain both CACertificateRefs and WellKnownCACertificates + rule: '!(has(self.caCertificateRefs) && size(self.caCertificateRefs) + > 0 && has(self.wellKnownCACertificates) && self.wellKnownCACertificates + != "")' + - message: must specify either CACertificateRefs or WellKnownCACertificates + rule: (has(self.caCertificateRefs) && size(self.caCertificateRefs) + > 0 || has(self.wellKnownCACertificates) && self.wellKnownCACertificates + != "") + required: + - targetRefs + - validation + type: object + status: + description: Status defines the current state of BackendTLSPolicy. + properties: + ancestors: + description: |- + Ancestors is a list of ancestor resources (usually Gateways) that are + associated with the policy, and the status of the policy with respect to + each ancestor. When this policy attaches to a parent, the controller that + manages the parent and the ancestors MUST add an entry to this list when + the controller first sees the policy and SHOULD update the entry as + appropriate when the relevant ancestor is modified. + + Note that choosing the relevant ancestor is left to the Policy designers; + an important part of Policy design is designing the right object level at + which to namespace this status. + + Note also that implementations MUST ONLY populate ancestor status for + the Ancestor resources they are responsible for. Implementations MUST + use the ControllerName field to uniquely identify the entries in this list + that they are responsible for. + + Note that to achieve this, the list of PolicyAncestorStatus structs + MUST be treated as a map with a composite key, made up of the AncestorRef + and ControllerName fields combined. + + A maximum of 16 ancestors will be represented in this list. An empty list + means the Policy is not relevant for any ancestors. + + If this slice is full, implementations MUST NOT add further entries. + Instead they MUST consider the policy unimplementable and signal that + on any related resources such as the ancestor that would be referenced + here. For example, if this list was full on BackendTLSPolicy, no + additional Gateways would be able to reference the Service targeted by + the BackendTLSPolicy. + items: + description: |- + PolicyAncestorStatus describes the status of a route with respect to an + associated Ancestor. + + Ancestors refer to objects that are either the Target of a policy or above it + in terms of object hierarchy. For example, if a policy targets a Service, the + Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and + the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most + useful object to place Policy status on, so we recommend that implementations + SHOULD use Gateway as the PolicyAncestorStatus object unless the designers + have a _very_ good reason otherwise. + + In the context of policy attachment, the Ancestor is used to distinguish which + resource results in a distinct application of this policy. For example, if a policy + targets a Service, it may have a distinct result per attached Gateway. + + Policies targeting the same resource may have different effects depending on the + ancestors of those resources. For example, different Gateways targeting the same + Service may have different capabilities, especially if they have different underlying + implementations. + + For example, in BackendTLSPolicy, the Policy attaches to a Service that is + used as a backend in a HTTPRoute that is itself attached to a Gateway. + In this case, the relevant object for status is the Gateway, and that is the + ancestor object referred to in this status. + + Note that a parent is also an ancestor, so for objects where the parent is the + relevant object for status, this struct SHOULD still be used. + + This struct is intended to be used in a slice that's effectively a map, + with a composite key made up of the AncestorRef and the ControllerName. + properties: + ancestorRef: + description: |- + AncestorRef corresponds with a ParentRef in the spec that this + PolicyAncestorStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: Conditions describes the status of the Policy with + respect to the given Ancestor. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - controllerName + type: object + maxItems: 16 + type: array + required: + - ancestors + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_gatewayclasses.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: gatewayclasses.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: GatewayClass + listKind: GatewayClassList + plural: gatewayclasses + shortNames: + - gc + singular: gatewayclass + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.controllerName + name: Controller + type: string + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.description + name: Description + priority: 1 + type: string + name: v1 + schema: + openAPIV3Schema: + description: |- + GatewayClass describes a class of Gateways available to the user for creating + Gateway resources. + + 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. This recommendation is intended to + limit the blast radius of changes to GatewayClass or associated parameters. + If implementations choose to propagate GatewayClass changes to existing + Gateways, that MUST be clearly documented by the implementation. + + Whenever one or more Gateways are using a GatewayClass, implementations SHOULD + add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the + associated GatewayClass. This ensures that a GatewayClass associated with a + Gateway is not deleted while in use. + + GatewayClass is a Cluster level resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClass. + properties: + controllerName: + description: |- + ControllerName is the name of the controller that is managing Gateways of + this class. The value of this field MUST be a domain prefixed path. + + Example: "example.net/gateway-controller". + + This field is not mutable and cannot be empty. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + description: + description: Description helps describe a GatewayClass with more details. + maxLength: 64 + type: string + parametersRef: + description: |- + ParametersRef is a reference to a resource that contains the configuration + parameters corresponding to the GatewayClass. This is optional if the + controller does not require any additional configuration. + + ParametersRef can reference a standard Kubernetes resource, i.e. ConfigMap, + or an implementation-specific custom resource. The resource can be + cluster-scoped or namespace-scoped. + + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the GatewayClass SHOULD be + rejected with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + + A Gateway for this GatewayClass may provide its own `parametersRef`. When both are specified, + the merging behavior is implementation specific. + It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway. + + Support: Implementation-specific + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. + This field is required when referring to a Namespace-scoped resource and + MUST be unset when referring to a Cluster-scoped resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + required: + - controllerName + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: |- + Status defines the current state of GatewayClass. + + Implementations MUST populate status on all GatewayClass resources which + specify their controller name. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: |- + Conditions is the current status from the controller for + this GatewayClass. + + Controllers should prefer to publish conditions using values + of GatewayClassConditionType for the type of each Condition. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + supportedFeatures: + description: |- + SupportedFeatures is the set of features the GatewayClass support. + It MUST be sorted in ascending alphabetical order by the Name key. + items: + properties: + name: + description: |- + FeatureName is used to describe distinct features that are covered by + conformance tests. + type: string + required: + - name + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.controllerName + name: Controller + type: string + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.description + name: Description + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: |- + GatewayClass describes a class of Gateways available to the user for creating + Gateway resources. + + 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. This recommendation is intended to + limit the blast radius of changes to GatewayClass or associated parameters. + If implementations choose to propagate GatewayClass changes to existing + Gateways, that MUST be clearly documented by the implementation. + + Whenever one or more Gateways are using a GatewayClass, implementations SHOULD + add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the + associated GatewayClass. This ensures that a GatewayClass associated with a + Gateway is not deleted while in use. + + GatewayClass is a Cluster level resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClass. + properties: + controllerName: + description: |- + ControllerName is the name of the controller that is managing Gateways of + this class. The value of this field MUST be a domain prefixed path. + + Example: "example.net/gateway-controller". + + This field is not mutable and cannot be empty. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + description: + description: Description helps describe a GatewayClass with more details. + maxLength: 64 + type: string + parametersRef: + description: |- + ParametersRef is a reference to a resource that contains the configuration + parameters corresponding to the GatewayClass. This is optional if the + controller does not require any additional configuration. + + ParametersRef can reference a standard Kubernetes resource, i.e. ConfigMap, + or an implementation-specific custom resource. The resource can be + cluster-scoped or namespace-scoped. + + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the GatewayClass SHOULD be + rejected with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + + A Gateway for this GatewayClass may provide its own `parametersRef`. When both are specified, + the merging behavior is implementation specific. + It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway. + + Support: Implementation-specific + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. + This field is required when referring to a Namespace-scoped resource and + MUST be unset when referring to a Cluster-scoped resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + required: + - controllerName + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: |- + Status defines the current state of GatewayClass. + + Implementations MUST populate status on all GatewayClass resources which + specify their controller name. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: |- + Conditions is the current status from the controller for + this GatewayClass. + + Controllers should prefer to publish conditions using values + of GatewayClassConditionType for the type of each Condition. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + supportedFeatures: + description: |- + SupportedFeatures is the set of features the GatewayClass support. + It MUST be sorted in ascending alphabetical order by the Name key. + items: + properties: + name: + description: |- + FeatureName is used to describe distinct features that are covered by + conformance tests. + type: string + required: + - name + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_gateways.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: gateways.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: Gateway + listKind: GatewayList + plural: gateways + shortNames: + - gtw + singular: gateway + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.gatewayClassName + name: Class + type: string + - jsonPath: .status.addresses[*].value + name: Address + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + Gateway represents an instance of a service-traffic handling infrastructure + by binding Listeners to a set of IP addresses. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of Gateway. + properties: + addresses: + description: |- + Addresses requested for this Gateway. This is optional and behavior can + depend on the implementation. If a value is set in the spec and the + requested address is invalid or unavailable, the implementation MUST + indicate this in the associated entry in GatewayStatus.Addresses. + + The Addresses field represents a request for the address(es) on the + "outside of the Gateway", that traffic bound for this Gateway will use. + This could be the IP address or hostname of an external load balancer or + other networking infrastructure, or some other address that traffic will + be sent to. + + If no Addresses are specified, the implementation MAY schedule the + Gateway in an implementation-specific manner, assigning an appropriate + set of Addresses. + + The implementation MUST bind all Listeners to every GatewayAddress that + it assigns to the Gateway and add a corresponding entry in + GatewayStatus.Addresses. + + Support: Extended + items: + description: GatewaySpecAddress describes an address that can be + bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: |- + When a value is unspecified, an implementation SHOULD automatically + assign an address matching the requested type if possible. + + If an implementation does not support an empty value, they MUST set the + "Programmed" condition in status to False with a reason of "AddressNotAssigned". + + Examples: `1.2.3.4`, `128::1`, `my-ip-address`. + maxLength: 253 + type: string + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching + ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + true' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: IPAddress values must be unique + rule: 'self.all(a1, a1.type == ''IPAddress'' ? self.exists_one(a2, + a2.type == a1.type && a2.value == a1.value) : true )' + - message: Hostname values must be unique + rule: 'self.all(a1, a1.type == ''Hostname'' ? self.exists_one(a2, + a2.type == a1.type && a2.value == a1.value) : true )' + allowedListeners: + description: |- + AllowedListeners defines which ListenerSets can be attached to this Gateway. + While this feature is experimental, the default value is to allow no ListenerSets. + properties: + namespaces: + default: + from: None + description: |- + Namespaces defines which namespaces ListenerSets can be attached to this Gateway. + While this feature is experimental, the default value is to allow no ListenerSets. + properties: + from: + default: None + description: |- + From indicates where ListenerSets can attach to this Gateway. Possible + values are: + + * Same: Only ListenerSets in the same namespace may be attached to this Gateway. + * Selector: ListenerSets in namespaces selected by the selector may be attached to this Gateway. + * All: ListenerSets in all namespaces may be attached to this Gateway. + * None: Only listeners defined in the Gateway's spec are allowed + + While this feature is experimental, the default value None + enum: + - All + - Selector + - Same + - None + type: string + selector: + description: |- + Selector must be specified when From is set to "Selector". In that case, + only ListenerSets in Namespaces matching this Selector will be selected by this + Gateway. This field is ignored for other values of "From". + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + type: object + type: object + backendTLS: + description: |- + BackendTLS configures TLS settings for when this Gateway is connecting to + backends with TLS. + + Support: Core + properties: + clientCertificateRef: + description: |- + ClientCertificateRef is a reference to an object that contains a Client + Certificate and the associated private key. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + + ClientCertificateRef can reference to standard Kubernetes resources, i.e. + Secret, or implementation-specific custom resources. + + This setting can be overridden on the service level by use of BackendTLSPolicy. + + Support: Core + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + type: object + gatewayClassName: + description: |- + GatewayClassName used for this Gateway. This is the name of a + GatewayClass resource. + maxLength: 253 + minLength: 1 + type: string + infrastructure: + description: |- + Infrastructure defines infrastructure level attributes about this Gateway instance. + + Support: Extended + properties: + annotations: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Annotations that SHOULD be applied to any resources created in response to this Gateway. + + For implementations creating other Kubernetes objects, this should be the `metadata.annotations` field on resources. + For other implementations, this refers to any relevant (implementation specific) "annotations" concepts. + + An implementation may chose to add additional implementation-specific annotations as they see fit. + + Support: Extended + maxProperties: 8 + type: object + x-kubernetes-validations: + - message: Annotation keys must be in the form of an optional + DNS subdomain prefix followed by a required name segment of + up to 63 characters. + rule: self.all(key, key.matches(r"""^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9]$""")) + - message: If specified, the annotation key's prefix must be a + DNS subdomain not longer than 253 characters in total. + rule: self.all(key, key.split("/")[0].size() < 253) + labels: + additionalProperties: + description: |- + LabelValue is the value of a label in the Gateway API. This is used for validation + of maps such as Gateway infrastructure labels. This matches the Kubernetes + label validation rules: + * must be 63 characters or less (can be empty), + * unless empty, must begin and end with an alphanumeric character ([a-z0-9A-Z]), + * could contain dashes (-), underscores (_), dots (.), and alphanumerics between. + + Valid values include: + + * MyValue + * my.name + * 123-my-value + maxLength: 63 + minLength: 0 + pattern: ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$ + type: string + description: |- + Labels that SHOULD be applied to any resources created in response to this Gateway. + + For implementations creating other Kubernetes objects, this should be the `metadata.labels` field on resources. + For other implementations, this refers to any relevant (implementation specific) "labels" concepts. + + An implementation may chose to add additional implementation-specific labels as they see fit. + + If an implementation maps these labels to Pods, or any other resource that would need to be recreated when labels + change, it SHOULD clearly warn about this behavior in documentation. + + Support: Extended + maxProperties: 8 + type: object + x-kubernetes-validations: + - message: Label keys must be in the form of an optional DNS subdomain + prefix followed by a required name segment of up to 63 characters. + rule: self.all(key, key.matches(r"""^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9]$""")) + - message: If specified, the label key's prefix must be a DNS + subdomain not longer than 253 characters in total. + rule: self.all(key, key.split("/")[0].size() < 253) + parametersRef: + description: |- + ParametersRef is a reference to a resource that contains the configuration + parameters corresponding to the Gateway. This is optional if the + controller does not require any additional configuration. + + This follows the same semantics as GatewayClass's `parametersRef`, but on a per-Gateway basis + + The Gateway's GatewayClass may provide its own `parametersRef`. When both are specified, + the merging behavior is implementation specific. + It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway. + + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the Gateway SHOULD be + rejected with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + + Support: Implementation-specific + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + type: object + listeners: + description: |- + Listeners associated with this Gateway. Listeners define + logical endpoints that are bound on this Gateway's addresses. + At least one Listener MUST be specified. + + ## Distinct Listeners + + Each Listener in a set of Listeners (for example, in a single Gateway) + MUST be _distinct_, in that a traffic flow MUST be able to be assigned to + exactly one listener. (This section uses "set of Listeners" rather than + "Listeners in a single Gateway" because implementations MAY merge configuration + from multiple Gateways onto a single data plane, and these rules _also_ + apply in that case). + + Practically, this means that each listener in a set MUST have a unique + combination of Port, Protocol, and, if supported by the protocol, Hostname. + + Some combinations of port, protocol, and TLS settings are considered + Core support and MUST be supported by implementations based on the objects + they support: + + HTTPRoute + + 1. HTTPRoute, Port: 80, Protocol: HTTP + 2. HTTPRoute, Port: 443, Protocol: HTTPS, TLS Mode: Terminate, TLS keypair provided + + TLSRoute + + 1. TLSRoute, Port: 443, Protocol: TLS, TLS Mode: Passthrough + + "Distinct" Listeners have the following property: + + **The implementation can match inbound requests to a single distinct + Listener**. + + When multiple Listeners share values for fields (for + example, two Listeners with the same Port value), the implementation + can match requests to only one of the Listeners using other + Listener fields. + + When multiple listeners have the same value for the Protocol field, then + each of the Listeners with matching Protocol values MUST have different + values for other fields. + + The set of fields that MUST be different for a Listener differs per protocol. + The following rules define the rules for what fields MUST be considered for + Listeners to be distinct with each protocol currently defined in the + Gateway API spec. + + The set of listeners that all share a protocol value MUST have _different_ + values for _at least one_ of these fields to be distinct: + + * **HTTP, HTTPS, TLS**: Port, Hostname + * **TCP, UDP**: Port + + One **very** important rule to call out involves what happens when an + implementation: + + * Supports TCP protocol Listeners, as well as HTTP, HTTPS, or TLS protocol + Listeners, and + * sees HTTP, HTTPS, or TLS protocols with the same `port` as one with TCP + Protocol. + + In this case all the Listeners that share a port with the + TCP Listener are not distinct and so MUST NOT be accepted. + + If an implementation does not support TCP Protocol Listeners, then the + previous rule does not apply, and the TCP Listeners SHOULD NOT be + accepted. + + Note that the `tls` field is not used for determining if a listener is distinct, because + Listeners that _only_ differ on TLS config will still conflict in all cases. + + ### Listeners that are distinct only by Hostname + + When the Listeners are distinct based only on Hostname, inbound request + hostnames MUST match from the most specific to least specific Hostname + values to choose the correct Listener and its associated set of Routes. + + Exact matches MUST be processed before wildcard matches, and wildcard + matches MUST be processed before fallback (empty Hostname value) + matches. For example, `"foo.example.com"` takes precedence over + `"*.example.com"`, and `"*.example.com"` takes precedence over `""`. + + Additionally, if there are multiple wildcard entries, more specific + wildcard entries must be processed before less specific wildcard entries. + For example, `"*.foo.example.com"` takes precedence over `"*.example.com"`. + + The precise definition here is that the higher the number of dots in the + hostname to the right of the wildcard character, the higher the precedence. + + The wildcard character will match any number of characters _and dots_ to + the left, however, so `"*.example.com"` will match both + `"foo.bar.example.com"` _and_ `"bar.example.com"`. + + ## Handling indistinct Listeners + + If a set of Listeners contains Listeners that are not distinct, then those + Listeners are _Conflicted_, and the implementation MUST set the "Conflicted" + condition in the Listener Status to "True". + + The words "indistinct" and "conflicted" are considered equivalent for the + purpose of this documentation. + + Implementations MAY choose to accept a Gateway with some Conflicted + Listeners only if they only accept the partial Listener set that contains + no Conflicted Listeners. + + Specifically, an implementation MAY accept a partial Listener set subject to + the following rules: + + * The implementation MUST NOT pick one conflicting Listener as the winner. + ALL indistinct Listeners must not be accepted for processing. + * At least one distinct Listener MUST be present, or else the Gateway effectively + contains _no_ Listeners, and must be rejected from processing as a whole. + + The implementation MUST set a "ListenersNotValid" condition on the + Gateway Status when the Gateway contains Conflicted Listeners whether or + not they accept the Gateway. That Condition SHOULD clearly + indicate in the Message which Listeners are conflicted, and which are + Accepted. Additionally, the Listener status for those listeners SHOULD + indicate which Listeners are conflicted and not Accepted. + + ## General Listener behavior + + Note that, for all distinct Listeners, requests SHOULD match at most one Listener. + For example, if Listeners are defined for "foo.example.com" and "*.example.com", a + request to "foo.example.com" SHOULD only be routed using routes attached + to the "foo.example.com" Listener (and not the "*.example.com" Listener). + + This concept is known as "Listener Isolation", and it is an Extended feature + of Gateway API. Implementations that do not support Listener Isolation MUST + clearly document this, and MUST NOT claim support for the + `GatewayHTTPListenerIsolation` feature. + + Implementations that _do_ support Listener Isolation SHOULD claim support + for the Extended `GatewayHTTPListenerIsolation` feature and pass the associated + conformance tests. + + ## Compatible Listeners + + A Gateway's Listeners are considered _compatible_ if: + + 1. They are distinct. + 2. The implementation can serve them in compliance with the Addresses + requirement that all Listeners are available on all assigned + addresses. + + Compatible combinations in Extended support are expected to vary across + implementations. A combination that is compatible for one implementation + may not be compatible for another. + + For example, an implementation that cannot serve both TCP and UDP listeners + on the same address, or cannot mix HTTPS and generic TLS listens on the same port + would not consider those cases compatible, even though they are distinct. + + Implementations MAY merge separate Gateways onto a single set of + Addresses if all Listeners across all Gateways are compatible. + + In a future release the MinItems=1 requirement MAY be dropped. + + Support: Core + items: + description: |- + Listener embodies the concept of a logical endpoint where a Gateway accepts + network connections. + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: |- + AllowedRoutes defines the types of routes that MAY be attached to a + Listener and the trusted namespaces where those Route resources MAY be + present. + + Although a client request may match multiple route rules, only one rule + may ultimately receive the request. Matching precedence MUST be + determined in order of the following criteria: + + * The most specific match as defined by the Route type. + * The oldest Route based on creation timestamp. For example, a Route with + a creation timestamp of "2020-09-08 01:02:03" is given precedence over + a Route with a creation timestamp of "2020-09-08 01:02:04". + * If everything else is equivalent, the Route appearing first in + alphabetical order (namespace/name) should be given precedence. For + example, foo/bar is given precedence over foo/baz. + + All valid rules within a Route attached to this Listener should be + implemented. Invalid Route rules can be ignored (sometimes that will mean + the full Route). If a Route rule transitions from valid to invalid, + support for that Route rule should be dropped to ensure consistency. For + example, even if a filter specified by a Route rule is invalid, the rest + of the rules within that Route should still be supported. + + Support: Core + properties: + kinds: + description: |- + Kinds specifies the groups and kinds of Routes that are allowed to bind + to this Gateway Listener. When unspecified or empty, the kinds of Routes + selected are determined using the Listener protocol. + + A RouteGroupKind MUST correspond to kinds of Routes that are compatible + with the application protocol specified in the Listener's Protocol field. + If an implementation does not support or recognize this resource type, it + MUST set the "ResolvedRefs" condition to False for this Listener with the + "InvalidRouteKinds" reason. + + Support: Core + items: + description: RouteGroupKind indicates the group and kind + of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: |- + Namespaces indicates namespaces from which Routes may be attached to this + Listener. This is restricted to the namespace of this Gateway by default. + + Support: Core + properties: + from: + default: Same + description: |- + From indicates where Routes will be selected for this Gateway. Possible + values are: + + * All: Routes in all namespaces may be used by this Gateway. + * Selector: Routes in namespaces selected by the selector may be used by + this Gateway. + * Same: Only Routes in the same namespace may be used by this Gateway. + + Support: Core + enum: + - All + - Selector + - Same + type: string + selector: + description: |- + Selector must be specified when From is set to "Selector". In that case, + only Routes in Namespaces matching this Selector will be selected by this + Gateway. This field is ignored for other values of "From". + + Support: Core + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + type: object + type: object + hostname: + description: |- + Hostname specifies the virtual hostname to match for protocol types that + define this concept. When unspecified, all hostnames are matched. This + field is ignored for protocols that don't require hostname based + matching. + + Implementations MUST apply Hostname matching appropriately for each of + the following protocols: + + * TLS: The Listener Hostname MUST match the SNI. + * HTTP: The Listener Hostname MUST match the Host header of the request. + * HTTPS: The Listener Hostname SHOULD match both the SNI and Host header. + Note that this does not require the SNI and Host header to be the same. + The semantics of this are described in more detail below. + + To ensure security, Section 11.1 of RFC-6066 emphasizes that server + implementations that rely on SNI hostname matching MUST also verify + hostnames within the application protocol. + + Section 9.1.2 of RFC-7540 provides a mechanism for servers to reject the + reuse of a connection by responding with the HTTP 421 Misdirected Request + status code. This indicates that the origin server has rejected the + request because it appears to have been misdirected. + + To detect misdirected requests, Gateways SHOULD match the authority of + the requests with all the SNI hostname(s) configured across all the + Gateway Listeners on the same port and protocol: + + * If another Listener has an exact match or more specific wildcard entry, + the Gateway SHOULD return a 421. + * If the current Listener (selected by SNI matching during ClientHello) + does not match the Host: + * If another Listener does match the Host the Gateway SHOULD return a + 421. + * If no other Listener matches the Host, the Gateway MUST return a + 404. + + For HTTPRoute and TLSRoute resources, there is an interaction with the + `spec.hostnames` array. When both listener and route specify hostnames, + there MUST be an intersection between the values for a Route to be + accepted. For more information, refer to the Route specific Hostnames + documentation. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + name: + description: |- + Name is the name of the Listener. This name MUST be unique within a + Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: |- + Port is the network port. Multiple listeners may use the + same port, subject to the Listener compatibility rules. + + Support: Core + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: |- + Protocol specifies the network protocol this listener expects to receive. + + Support: Core + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: |- + TLS is the TLS configuration for the Listener. This field is required if + the Protocol field is "HTTPS" or "TLS". It is invalid to set this field + if the Protocol field is "HTTP", "TCP", or "UDP". + + The association of SNIs to Certificate defined in GatewayTLSConfig is + defined based on the Hostname field for this listener. + + The GatewayClass MUST use the longest matching SNI out of all + available certificates for any TLS handshake. + + Support: Core + properties: + certificateRefs: + description: |- + CertificateRefs contains a series of references to Kubernetes objects that + contains TLS certificates and private keys. These certificates are used to + establish a TLS handshake for requests that match the hostname of the + associated listener. + + A single CertificateRef to a Kubernetes Secret has "Core" support. + Implementations MAY choose to support attaching multiple certificates to + a Listener, but this behavior is implementation-specific. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + + This field is required to have at least one element when the mode is set + to "Terminate" (default) and is optional otherwise. + + CertificateRefs can reference to standard Kubernetes resources, i.e. + Secret, or implementation-specific custom resources. + + Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls + + Support: Implementation-specific (More than one reference or other resource types) + items: + description: |- + SecretObjectReference identifies an API object including its namespace, + defaulting to Secret. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example + "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + frontendValidation: + description: |- + FrontendValidation holds configuration information for validating the frontend (client). + Setting this field will require clients to send a client certificate + required for validation during the TLS handshake. In browsers this may result in a dialog appearing + that requests a user to specify the client certificate. + The maximum depth of a certificate chain accepted in verification is Implementation specific. + + Support: Extended + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to + Kubernetes objects that contain TLS certificates of + the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the client. + + A single CA certificate reference to a Kubernetes ConfigMap + has "Core" support. + Implementations MAY choose to support attaching multiple CA certificates to + a Listener, but this behavior is implementation-specific. + + Support: Core - A single reference to a Kubernetes ConfigMap + with the CA certificate in a key named `ca.crt`. + + Support: Implementation-specific (More than one reference, or other kinds + of resources). + + References to a resource in a different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + items: + description: |- + ObjectReference identifies an API object including its namespace. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When set to the empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "ConfigMap" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + maxItems: 8 + minItems: 1 + type: array + type: object + mode: + default: Terminate + description: |- + Mode defines the TLS behavior for the TLS session initiated by the client. + There are two possible modes: + + - Terminate: The TLS session between the downstream client and the + Gateway is terminated at the Gateway. This mode requires certificates + to be specified in some way, such as populating the certificateRefs + field. + - Passthrough: The TLS session is NOT terminated by the Gateway. This + implies that the Gateway can't decipher the TLS stream except for + the ClientHello message of the TLS protocol. The certificateRefs field + is ignored in this mode. + + Support: Core + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Options are a list of key/value pairs to enable extended TLS + configuration for each implementation. For example, configuring the + minimum TLS version or supported cipher suites. + + A set of common keys MAY be defined by the API in the future. To avoid + any ambiguity, implementation-specific definitions MUST use + domain-prefixed names, such as `example.com/my-custom-option`. + Un-prefixed names are reserved for key names defined by Gateway API. + + Support: Implementation-specific + maxProperties: 16 + type: object + type: object + x-kubernetes-validations: + - message: certificateRefs or options must be specified when + mode is Terminate + rule: 'self.mode == ''Terminate'' ? size(self.certificateRefs) + > 0 || size(self.options) > 0 : true' + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: tls must not be specified for protocols ['HTTP', 'TCP', + 'UDP'] + rule: 'self.all(l, l.protocol in [''HTTP'', ''TCP'', ''UDP''] ? + !has(l.tls) : true)' + - message: tls mode must be Terminate for protocol HTTPS + rule: 'self.all(l, (l.protocol == ''HTTPS'' && has(l.tls)) ? (l.tls.mode + == '''' || l.tls.mode == ''Terminate'') : true)' + - message: hostname must not be specified for protocols ['TCP', 'UDP'] + rule: 'self.all(l, l.protocol in [''TCP'', ''UDP''] ? (!has(l.hostname) + || l.hostname == '''') : true)' + - message: Listener name must be unique within the Gateway + rule: self.all(l1, self.exists_one(l2, l1.name == l2.name)) + - message: Combination of port, protocol and hostname must be unique + for each listener + rule: 'self.all(l1, self.exists_one(l2, l1.port == l2.port && l1.protocol + == l2.protocol && (has(l1.hostname) && has(l2.hostname) ? l1.hostname + == l2.hostname : !has(l1.hostname) && !has(l2.hostname))))' + required: + - gatewayClassName + - listeners + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: Status defines the current state of Gateway. + properties: + addresses: + description: |- + Addresses lists the network addresses that have been bound to the + Gateway. + + This list may differ from the addresses provided in the spec under some + conditions: + + * no addresses are specified, all addresses are dynamically assigned + * a combination of specified and dynamic addresses are assigned + * a specified address was unusable (e.g. already in use) + items: + description: GatewayStatusAddress describes a network address that + is bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: |- + Value of the address. The validity of the values will depend + on the type and support by the controller. + + Examples: `1.2.3.4`, `128::1`, `my-ip-address`. + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching + ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + true' + maxItems: 16 + type: array + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: |- + Conditions describe the current conditions of the Gateway. + + Implementations should prefer to express Gateway conditions + using the `GatewayConditionType` and `GatewayConditionReason` + constants so that operators and tools can converge on a common + vocabulary to describe Gateway state. + + Known condition types are: + + * "Accepted" + * "Programmed" + * "Ready" + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port + defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: |- + AttachedRoutes represents the total number of Routes that have been + successfully attached to this Listener. + + Successful attachment of a Route to a Listener is based solely on the + combination of the AllowedRoutes field on the corresponding Listener + and the Route's ParentRefs field. A Route is successfully attached to + a Listener when it is selected by the Listener's AllowedRoutes field + AND the Route has a valid ParentRef selecting the whole Gateway + resource or a specific Listener as a parent resource (more detail on + attachment semantics can be found in the documentation on the various + Route kinds ParentRefs fields). Listener or Route status does not impact + successful attachment, i.e. the AttachedRoutes field count MUST be set + for Listeners with condition Accepted: false and MUST count successfully + attached Routes that may themselves have Accepted: false conditions. + + Uses for this field include troubleshooting Route attachment and + measuring blast radius/impact of changes to a Listener. + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this + listener. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status + corresponds to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + supportedKinds: + description: |- + SupportedKinds is the list indicating the Kinds supported by this + listener. This MUST represent the kinds an implementation supports for + that Listener configuration. + + If kinds are specified in Spec that are not supported, they MUST NOT + appear in this list and an implementation MUST set the "ResolvedRefs" + condition to "False" with the "InvalidRouteKinds" reason. If both valid + and invalid Route kinds are specified, the implementation MUST + reference the valid Route kinds that have been specified. + items: + description: RouteGroupKind indicates the group and kind of + a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.gatewayClassName + name: Class + type: string + - jsonPath: .status.addresses[*].value + name: Address + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: |- + Gateway represents an instance of a service-traffic handling infrastructure + by binding Listeners to a set of IP addresses. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of Gateway. + properties: + addresses: + description: |- + Addresses requested for this Gateway. This is optional and behavior can + depend on the implementation. If a value is set in the spec and the + requested address is invalid or unavailable, the implementation MUST + indicate this in the associated entry in GatewayStatus.Addresses. + + The Addresses field represents a request for the address(es) on the + "outside of the Gateway", that traffic bound for this Gateway will use. + This could be the IP address or hostname of an external load balancer or + other networking infrastructure, or some other address that traffic will + be sent to. + + If no Addresses are specified, the implementation MAY schedule the + Gateway in an implementation-specific manner, assigning an appropriate + set of Addresses. + + The implementation MUST bind all Listeners to every GatewayAddress that + it assigns to the Gateway and add a corresponding entry in + GatewayStatus.Addresses. + + Support: Extended + items: + description: GatewaySpecAddress describes an address that can be + bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: |- + When a value is unspecified, an implementation SHOULD automatically + assign an address matching the requested type if possible. + + If an implementation does not support an empty value, they MUST set the + "Programmed" condition in status to False with a reason of "AddressNotAssigned". + + Examples: `1.2.3.4`, `128::1`, `my-ip-address`. + maxLength: 253 + type: string + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching + ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + true' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: IPAddress values must be unique + rule: 'self.all(a1, a1.type == ''IPAddress'' ? self.exists_one(a2, + a2.type == a1.type && a2.value == a1.value) : true )' + - message: Hostname values must be unique + rule: 'self.all(a1, a1.type == ''Hostname'' ? self.exists_one(a2, + a2.type == a1.type && a2.value == a1.value) : true )' + allowedListeners: + description: |- + AllowedListeners defines which ListenerSets can be attached to this Gateway. + While this feature is experimental, the default value is to allow no ListenerSets. + properties: + namespaces: + default: + from: None + description: |- + Namespaces defines which namespaces ListenerSets can be attached to this Gateway. + While this feature is experimental, the default value is to allow no ListenerSets. + properties: + from: + default: None + description: |- + From indicates where ListenerSets can attach to this Gateway. Possible + values are: + + * Same: Only ListenerSets in the same namespace may be attached to this Gateway. + * Selector: ListenerSets in namespaces selected by the selector may be attached to this Gateway. + * All: ListenerSets in all namespaces may be attached to this Gateway. + * None: Only listeners defined in the Gateway's spec are allowed + + While this feature is experimental, the default value None + enum: + - All + - Selector + - Same + - None + type: string + selector: + description: |- + Selector must be specified when From is set to "Selector". In that case, + only ListenerSets in Namespaces matching this Selector will be selected by this + Gateway. This field is ignored for other values of "From". + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + type: object + type: object + backendTLS: + description: |- + BackendTLS configures TLS settings for when this Gateway is connecting to + backends with TLS. + + Support: Core + properties: + clientCertificateRef: + description: |- + ClientCertificateRef is a reference to an object that contains a Client + Certificate and the associated private key. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + + ClientCertificateRef can reference to standard Kubernetes resources, i.e. + Secret, or implementation-specific custom resources. + + This setting can be overridden on the service level by use of BackendTLSPolicy. + + Support: Core + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + type: object + gatewayClassName: + description: |- + GatewayClassName used for this Gateway. This is the name of a + GatewayClass resource. + maxLength: 253 + minLength: 1 + type: string + infrastructure: + description: |- + Infrastructure defines infrastructure level attributes about this Gateway instance. + + Support: Extended + properties: + annotations: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Annotations that SHOULD be applied to any resources created in response to this Gateway. + + For implementations creating other Kubernetes objects, this should be the `metadata.annotations` field on resources. + For other implementations, this refers to any relevant (implementation specific) "annotations" concepts. + + An implementation may chose to add additional implementation-specific annotations as they see fit. + + Support: Extended + maxProperties: 8 + type: object + x-kubernetes-validations: + - message: Annotation keys must be in the form of an optional + DNS subdomain prefix followed by a required name segment of + up to 63 characters. + rule: self.all(key, key.matches(r"""^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9]$""")) + - message: If specified, the annotation key's prefix must be a + DNS subdomain not longer than 253 characters in total. + rule: self.all(key, key.split("/")[0].size() < 253) + labels: + additionalProperties: + description: |- + LabelValue is the value of a label in the Gateway API. This is used for validation + of maps such as Gateway infrastructure labels. This matches the Kubernetes + label validation rules: + * must be 63 characters or less (can be empty), + * unless empty, must begin and end with an alphanumeric character ([a-z0-9A-Z]), + * could contain dashes (-), underscores (_), dots (.), and alphanumerics between. + + Valid values include: + + * MyValue + * my.name + * 123-my-value + maxLength: 63 + minLength: 0 + pattern: ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$ + type: string + description: |- + Labels that SHOULD be applied to any resources created in response to this Gateway. + + For implementations creating other Kubernetes objects, this should be the `metadata.labels` field on resources. + For other implementations, this refers to any relevant (implementation specific) "labels" concepts. + + An implementation may chose to add additional implementation-specific labels as they see fit. + + If an implementation maps these labels to Pods, or any other resource that would need to be recreated when labels + change, it SHOULD clearly warn about this behavior in documentation. + + Support: Extended + maxProperties: 8 + type: object + x-kubernetes-validations: + - message: Label keys must be in the form of an optional DNS subdomain + prefix followed by a required name segment of up to 63 characters. + rule: self.all(key, key.matches(r"""^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9]$""")) + - message: If specified, the label key's prefix must be a DNS + subdomain not longer than 253 characters in total. + rule: self.all(key, key.split("/")[0].size() < 253) + parametersRef: + description: |- + ParametersRef is a reference to a resource that contains the configuration + parameters corresponding to the Gateway. This is optional if the + controller does not require any additional configuration. + + This follows the same semantics as GatewayClass's `parametersRef`, but on a per-Gateway basis + + The Gateway's GatewayClass may provide its own `parametersRef`. When both are specified, + the merging behavior is implementation specific. + It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway. + + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the Gateway SHOULD be + rejected with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + + Support: Implementation-specific + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + type: object + listeners: + description: |- + Listeners associated with this Gateway. Listeners define + logical endpoints that are bound on this Gateway's addresses. + At least one Listener MUST be specified. + + ## Distinct Listeners + + Each Listener in a set of Listeners (for example, in a single Gateway) + MUST be _distinct_, in that a traffic flow MUST be able to be assigned to + exactly one listener. (This section uses "set of Listeners" rather than + "Listeners in a single Gateway" because implementations MAY merge configuration + from multiple Gateways onto a single data plane, and these rules _also_ + apply in that case). + + Practically, this means that each listener in a set MUST have a unique + combination of Port, Protocol, and, if supported by the protocol, Hostname. + + Some combinations of port, protocol, and TLS settings are considered + Core support and MUST be supported by implementations based on the objects + they support: + + HTTPRoute + + 1. HTTPRoute, Port: 80, Protocol: HTTP + 2. HTTPRoute, Port: 443, Protocol: HTTPS, TLS Mode: Terminate, TLS keypair provided + + TLSRoute + + 1. TLSRoute, Port: 443, Protocol: TLS, TLS Mode: Passthrough + + "Distinct" Listeners have the following property: + + **The implementation can match inbound requests to a single distinct + Listener**. + + When multiple Listeners share values for fields (for + example, two Listeners with the same Port value), the implementation + can match requests to only one of the Listeners using other + Listener fields. + + When multiple listeners have the same value for the Protocol field, then + each of the Listeners with matching Protocol values MUST have different + values for other fields. + + The set of fields that MUST be different for a Listener differs per protocol. + The following rules define the rules for what fields MUST be considered for + Listeners to be distinct with each protocol currently defined in the + Gateway API spec. + + The set of listeners that all share a protocol value MUST have _different_ + values for _at least one_ of these fields to be distinct: + + * **HTTP, HTTPS, TLS**: Port, Hostname + * **TCP, UDP**: Port + + One **very** important rule to call out involves what happens when an + implementation: + + * Supports TCP protocol Listeners, as well as HTTP, HTTPS, or TLS protocol + Listeners, and + * sees HTTP, HTTPS, or TLS protocols with the same `port` as one with TCP + Protocol. + + In this case all the Listeners that share a port with the + TCP Listener are not distinct and so MUST NOT be accepted. + + If an implementation does not support TCP Protocol Listeners, then the + previous rule does not apply, and the TCP Listeners SHOULD NOT be + accepted. + + Note that the `tls` field is not used for determining if a listener is distinct, because + Listeners that _only_ differ on TLS config will still conflict in all cases. + + ### Listeners that are distinct only by Hostname + + When the Listeners are distinct based only on Hostname, inbound request + hostnames MUST match from the most specific to least specific Hostname + values to choose the correct Listener and its associated set of Routes. + + Exact matches MUST be processed before wildcard matches, and wildcard + matches MUST be processed before fallback (empty Hostname value) + matches. For example, `"foo.example.com"` takes precedence over + `"*.example.com"`, and `"*.example.com"` takes precedence over `""`. + + Additionally, if there are multiple wildcard entries, more specific + wildcard entries must be processed before less specific wildcard entries. + For example, `"*.foo.example.com"` takes precedence over `"*.example.com"`. + + The precise definition here is that the higher the number of dots in the + hostname to the right of the wildcard character, the higher the precedence. + + The wildcard character will match any number of characters _and dots_ to + the left, however, so `"*.example.com"` will match both + `"foo.bar.example.com"` _and_ `"bar.example.com"`. + + ## Handling indistinct Listeners + + If a set of Listeners contains Listeners that are not distinct, then those + Listeners are _Conflicted_, and the implementation MUST set the "Conflicted" + condition in the Listener Status to "True". + + The words "indistinct" and "conflicted" are considered equivalent for the + purpose of this documentation. + + Implementations MAY choose to accept a Gateway with some Conflicted + Listeners only if they only accept the partial Listener set that contains + no Conflicted Listeners. + + Specifically, an implementation MAY accept a partial Listener set subject to + the following rules: + + * The implementation MUST NOT pick one conflicting Listener as the winner. + ALL indistinct Listeners must not be accepted for processing. + * At least one distinct Listener MUST be present, or else the Gateway effectively + contains _no_ Listeners, and must be rejected from processing as a whole. + + The implementation MUST set a "ListenersNotValid" condition on the + Gateway Status when the Gateway contains Conflicted Listeners whether or + not they accept the Gateway. That Condition SHOULD clearly + indicate in the Message which Listeners are conflicted, and which are + Accepted. Additionally, the Listener status for those listeners SHOULD + indicate which Listeners are conflicted and not Accepted. + + ## General Listener behavior + + Note that, for all distinct Listeners, requests SHOULD match at most one Listener. + For example, if Listeners are defined for "foo.example.com" and "*.example.com", a + request to "foo.example.com" SHOULD only be routed using routes attached + to the "foo.example.com" Listener (and not the "*.example.com" Listener). + + This concept is known as "Listener Isolation", and it is an Extended feature + of Gateway API. Implementations that do not support Listener Isolation MUST + clearly document this, and MUST NOT claim support for the + `GatewayHTTPListenerIsolation` feature. + + Implementations that _do_ support Listener Isolation SHOULD claim support + for the Extended `GatewayHTTPListenerIsolation` feature and pass the associated + conformance tests. + + ## Compatible Listeners + + A Gateway's Listeners are considered _compatible_ if: + + 1. They are distinct. + 2. The implementation can serve them in compliance with the Addresses + requirement that all Listeners are available on all assigned + addresses. + + Compatible combinations in Extended support are expected to vary across + implementations. A combination that is compatible for one implementation + may not be compatible for another. + + For example, an implementation that cannot serve both TCP and UDP listeners + on the same address, or cannot mix HTTPS and generic TLS listens on the same port + would not consider those cases compatible, even though they are distinct. + + Implementations MAY merge separate Gateways onto a single set of + Addresses if all Listeners across all Gateways are compatible. + + In a future release the MinItems=1 requirement MAY be dropped. + + Support: Core + items: + description: |- + Listener embodies the concept of a logical endpoint where a Gateway accepts + network connections. + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: |- + AllowedRoutes defines the types of routes that MAY be attached to a + Listener and the trusted namespaces where those Route resources MAY be + present. + + Although a client request may match multiple route rules, only one rule + may ultimately receive the request. Matching precedence MUST be + determined in order of the following criteria: + + * The most specific match as defined by the Route type. + * The oldest Route based on creation timestamp. For example, a Route with + a creation timestamp of "2020-09-08 01:02:03" is given precedence over + a Route with a creation timestamp of "2020-09-08 01:02:04". + * If everything else is equivalent, the Route appearing first in + alphabetical order (namespace/name) should be given precedence. For + example, foo/bar is given precedence over foo/baz. + + All valid rules within a Route attached to this Listener should be + implemented. Invalid Route rules can be ignored (sometimes that will mean + the full Route). If a Route rule transitions from valid to invalid, + support for that Route rule should be dropped to ensure consistency. For + example, even if a filter specified by a Route rule is invalid, the rest + of the rules within that Route should still be supported. + + Support: Core + properties: + kinds: + description: |- + Kinds specifies the groups and kinds of Routes that are allowed to bind + to this Gateway Listener. When unspecified or empty, the kinds of Routes + selected are determined using the Listener protocol. + + A RouteGroupKind MUST correspond to kinds of Routes that are compatible + with the application protocol specified in the Listener's Protocol field. + If an implementation does not support or recognize this resource type, it + MUST set the "ResolvedRefs" condition to False for this Listener with the + "InvalidRouteKinds" reason. + + Support: Core + items: + description: RouteGroupKind indicates the group and kind + of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: |- + Namespaces indicates namespaces from which Routes may be attached to this + Listener. This is restricted to the namespace of this Gateway by default. + + Support: Core + properties: + from: + default: Same + description: |- + From indicates where Routes will be selected for this Gateway. Possible + values are: + + * All: Routes in all namespaces may be used by this Gateway. + * Selector: Routes in namespaces selected by the selector may be used by + this Gateway. + * Same: Only Routes in the same namespace may be used by this Gateway. + + Support: Core + enum: + - All + - Selector + - Same + type: string + selector: + description: |- + Selector must be specified when From is set to "Selector". In that case, + only Routes in Namespaces matching this Selector will be selected by this + Gateway. This field is ignored for other values of "From". + + Support: Core + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + type: object + type: object + hostname: + description: |- + Hostname specifies the virtual hostname to match for protocol types that + define this concept. When unspecified, all hostnames are matched. This + field is ignored for protocols that don't require hostname based + matching. + + Implementations MUST apply Hostname matching appropriately for each of + the following protocols: + + * TLS: The Listener Hostname MUST match the SNI. + * HTTP: The Listener Hostname MUST match the Host header of the request. + * HTTPS: The Listener Hostname SHOULD match both the SNI and Host header. + Note that this does not require the SNI and Host header to be the same. + The semantics of this are described in more detail below. + + To ensure security, Section 11.1 of RFC-6066 emphasizes that server + implementations that rely on SNI hostname matching MUST also verify + hostnames within the application protocol. + + Section 9.1.2 of RFC-7540 provides a mechanism for servers to reject the + reuse of a connection by responding with the HTTP 421 Misdirected Request + status code. This indicates that the origin server has rejected the + request because it appears to have been misdirected. + + To detect misdirected requests, Gateways SHOULD match the authority of + the requests with all the SNI hostname(s) configured across all the + Gateway Listeners on the same port and protocol: + + * If another Listener has an exact match or more specific wildcard entry, + the Gateway SHOULD return a 421. + * If the current Listener (selected by SNI matching during ClientHello) + does not match the Host: + * If another Listener does match the Host the Gateway SHOULD return a + 421. + * If no other Listener matches the Host, the Gateway MUST return a + 404. + + For HTTPRoute and TLSRoute resources, there is an interaction with the + `spec.hostnames` array. When both listener and route specify hostnames, + there MUST be an intersection between the values for a Route to be + accepted. For more information, refer to the Route specific Hostnames + documentation. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + name: + description: |- + Name is the name of the Listener. This name MUST be unique within a + Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: |- + Port is the network port. Multiple listeners may use the + same port, subject to the Listener compatibility rules. + + Support: Core + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: |- + Protocol specifies the network protocol this listener expects to receive. + + Support: Core + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: |- + TLS is the TLS configuration for the Listener. This field is required if + the Protocol field is "HTTPS" or "TLS". It is invalid to set this field + if the Protocol field is "HTTP", "TCP", or "UDP". + + The association of SNIs to Certificate defined in GatewayTLSConfig is + defined based on the Hostname field for this listener. + + The GatewayClass MUST use the longest matching SNI out of all + available certificates for any TLS handshake. + + Support: Core + properties: + certificateRefs: + description: |- + CertificateRefs contains a series of references to Kubernetes objects that + contains TLS certificates and private keys. These certificates are used to + establish a TLS handshake for requests that match the hostname of the + associated listener. + + A single CertificateRef to a Kubernetes Secret has "Core" support. + Implementations MAY choose to support attaching multiple certificates to + a Listener, but this behavior is implementation-specific. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + + This field is required to have at least one element when the mode is set + to "Terminate" (default) and is optional otherwise. + + CertificateRefs can reference to standard Kubernetes resources, i.e. + Secret, or implementation-specific custom resources. + + Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls + + Support: Implementation-specific (More than one reference or other resource types) + items: + description: |- + SecretObjectReference identifies an API object including its namespace, + defaulting to Secret. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example + "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + frontendValidation: + description: |- + FrontendValidation holds configuration information for validating the frontend (client). + Setting this field will require clients to send a client certificate + required for validation during the TLS handshake. In browsers this may result in a dialog appearing + that requests a user to specify the client certificate. + The maximum depth of a certificate chain accepted in verification is Implementation specific. + + Support: Extended + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to + Kubernetes objects that contain TLS certificates of + the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the client. + + A single CA certificate reference to a Kubernetes ConfigMap + has "Core" support. + Implementations MAY choose to support attaching multiple CA certificates to + a Listener, but this behavior is implementation-specific. + + Support: Core - A single reference to a Kubernetes ConfigMap + with the CA certificate in a key named `ca.crt`. + + Support: Implementation-specific (More than one reference, or other kinds + of resources). + + References to a resource in a different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + items: + description: |- + ObjectReference identifies an API object including its namespace. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When set to the empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "ConfigMap" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + maxItems: 8 + minItems: 1 + type: array + type: object + mode: + default: Terminate + description: |- + Mode defines the TLS behavior for the TLS session initiated by the client. + There are two possible modes: + + - Terminate: The TLS session between the downstream client and the + Gateway is terminated at the Gateway. This mode requires certificates + to be specified in some way, such as populating the certificateRefs + field. + - Passthrough: The TLS session is NOT terminated by the Gateway. This + implies that the Gateway can't decipher the TLS stream except for + the ClientHello message of the TLS protocol. The certificateRefs field + is ignored in this mode. + + Support: Core + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Options are a list of key/value pairs to enable extended TLS + configuration for each implementation. For example, configuring the + minimum TLS version or supported cipher suites. + + A set of common keys MAY be defined by the API in the future. To avoid + any ambiguity, implementation-specific definitions MUST use + domain-prefixed names, such as `example.com/my-custom-option`. + Un-prefixed names are reserved for key names defined by Gateway API. + + Support: Implementation-specific + maxProperties: 16 + type: object + type: object + x-kubernetes-validations: + - message: certificateRefs or options must be specified when + mode is Terminate + rule: 'self.mode == ''Terminate'' ? size(self.certificateRefs) + > 0 || size(self.options) > 0 : true' + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: tls must not be specified for protocols ['HTTP', 'TCP', + 'UDP'] + rule: 'self.all(l, l.protocol in [''HTTP'', ''TCP'', ''UDP''] ? + !has(l.tls) : true)' + - message: tls mode must be Terminate for protocol HTTPS + rule: 'self.all(l, (l.protocol == ''HTTPS'' && has(l.tls)) ? (l.tls.mode + == '''' || l.tls.mode == ''Terminate'') : true)' + - message: hostname must not be specified for protocols ['TCP', 'UDP'] + rule: 'self.all(l, l.protocol in [''TCP'', ''UDP''] ? (!has(l.hostname) + || l.hostname == '''') : true)' + - message: Listener name must be unique within the Gateway + rule: self.all(l1, self.exists_one(l2, l1.name == l2.name)) + - message: Combination of port, protocol and hostname must be unique + for each listener + rule: 'self.all(l1, self.exists_one(l2, l1.port == l2.port && l1.protocol + == l2.protocol && (has(l1.hostname) && has(l2.hostname) ? l1.hostname + == l2.hostname : !has(l1.hostname) && !has(l2.hostname))))' + required: + - gatewayClassName + - listeners + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: Status defines the current state of Gateway. + properties: + addresses: + description: |- + Addresses lists the network addresses that have been bound to the + Gateway. + + This list may differ from the addresses provided in the spec under some + conditions: + + * no addresses are specified, all addresses are dynamically assigned + * a combination of specified and dynamic addresses are assigned + * a specified address was unusable (e.g. already in use) + items: + description: GatewayStatusAddress describes a network address that + is bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: |- + Value of the address. The validity of the values will depend + on the type and support by the controller. + + Examples: `1.2.3.4`, `128::1`, `my-ip-address`. + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching + ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + true' + maxItems: 16 + type: array + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: |- + Conditions describe the current conditions of the Gateway. + + Implementations should prefer to express Gateway conditions + using the `GatewayConditionType` and `GatewayConditionReason` + constants so that operators and tools can converge on a common + vocabulary to describe Gateway state. + + Known condition types are: + + * "Accepted" + * "Programmed" + * "Ready" + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port + defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: |- + AttachedRoutes represents the total number of Routes that have been + successfully attached to this Listener. + + Successful attachment of a Route to a Listener is based solely on the + combination of the AllowedRoutes field on the corresponding Listener + and the Route's ParentRefs field. A Route is successfully attached to + a Listener when it is selected by the Listener's AllowedRoutes field + AND the Route has a valid ParentRef selecting the whole Gateway + resource or a specific Listener as a parent resource (more detail on + attachment semantics can be found in the documentation on the various + Route kinds ParentRefs fields). Listener or Route status does not impact + successful attachment, i.e. the AttachedRoutes field count MUST be set + for Listeners with condition Accepted: false and MUST count successfully + attached Routes that may themselves have Accepted: false conditions. + + Uses for this field include troubleshooting Route attachment and + measuring blast radius/impact of changes to a Listener. + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this + listener. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status + corresponds to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + supportedKinds: + description: |- + SupportedKinds is the list indicating the Kinds supported by this + listener. This MUST represent the kinds an implementation supports for + that Listener configuration. + + If kinds are specified in Spec that are not supported, they MUST NOT + appear in this list and an implementation MUST set the "ResolvedRefs" + condition to "False" with the "InvalidRouteKinds" reason. If both valid + and invalid Route kinds are specified, the implementation MUST + reference the valid Route kinds that have been specified. + items: + description: RouteGroupKind indicates the group and kind of + a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_grpcroutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: grpcroutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: GRPCRoute + listKind: GRPCRouteList + plural: grpcroutes + singular: grpcroute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + GRPCRoute provides a way to route gRPC requests. This includes the capability + to match requests by hostname, gRPC service, gRPC method, or HTTP/2 header. + Filters can be used to specify additional processing steps. Backends specify + where matching requests will be routed. + + GRPCRoute falls under extended support within the Gateway API. Within the + following specification, the word "MUST" indicates that an implementation + supporting GRPCRoute must conform to the indicated requirement, but an + implementation not supporting this route type need not follow the requirement + unless explicitly indicated. + + Implementations supporting `GRPCRoute` with the `HTTPS` `ProtocolType` MUST + accept HTTP/2 connections without an initial upgrade from HTTP/1.1, i.e. via + ALPN. If the implementation does not support this, then it MUST set the + "Accepted" condition to "False" for the affected listener with a reason of + "UnsupportedProtocol". Implementations MAY also accept HTTP/2 connections + with an upgrade from HTTP/1. + + Implementations supporting `GRPCRoute` with the `HTTP` `ProtocolType` MUST + support HTTP/2 over cleartext TCP (h2c, + https://www.rfc-editor.org/rfc/rfc7540#section-3.1) without an initial + upgrade from HTTP/1.1, i.e. with prior knowledge + (https://www.rfc-editor.org/rfc/rfc7540#section-3.4). If the implementation + does not support this, then it MUST set the "Accepted" condition to "False" + for the affected listener with a reason of "UnsupportedProtocol". + Implementations MAY also accept HTTP/2 connections with an upgrade from + HTTP/1, i.e. without prior knowledge. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GRPCRoute. + properties: + hostnames: + description: |- + Hostnames defines a set of hostnames to match against the GRPC + Host header to select a GRPCRoute to process the request. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label MUST appear by itself as the first label. + + If a hostname is specified by both the Listener and GRPCRoute, there + MUST be at least one intersecting hostname for the GRPCRoute to be + attached to the Listener. For example: + + * A Listener with `test.example.com` as the hostname matches GRPCRoutes + that have either not specified any hostnames, or have specified at + least one of `test.example.com` or `*.example.com`. + * A Listener with `*.example.com` as the hostname matches GRPCRoutes + that have either not specified any hostnames or have specified at least + one hostname that matches the Listener hostname. For example, + `test.example.com` and `*.example.com` would both match. On the other + hand, `example.com` and `test.example.net` would not match. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + + If both the Listener and GRPCRoute have specified hostnames, any + GRPCRoute hostnames that do not match the Listener hostname MUST be + ignored. For example, if a Listener specified `*.example.com`, and the + GRPCRoute specified `test.example.com` and `test.example.net`, + `test.example.net` MUST NOT be considered for a match. + + If both the Listener and GRPCRoute have specified hostnames, and none + match with the criteria above, then the GRPCRoute MUST NOT be accepted by + the implementation. The implementation MUST raise an 'Accepted' Condition + with a status of `False` in the corresponding RouteParentStatus. + + If a Route (A) of type HTTPRoute or GRPCRoute is attached to a + Listener and that listener already has another Route (B) of the other + type attached and the intersection of the hostnames of A and B is + non-empty, then the implementation MUST accept exactly one of these two + routes, determined by the following criteria, in order: + + * The oldest Route based on creation timestamp. + * The Route appearing first in alphabetical order by + "{namespace}/{name}". + + The rejected Route MUST raise an 'Accepted' condition with a status of + 'False' in the corresponding RouteParentStatus. + + Support: Core + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) + rules: + description: Rules are a list of GRPC matchers, filters and actions. + items: + description: |- + GRPCRouteRule defines the semantics for matching a gRPC request based on + conditions (matches), processing it (filters), and forwarding the request to + an API object (backendRefs). + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. + + Failure behavior here depends on how many BackendRefs are specified and + how many are invalid. + + If *all* entries in BackendRefs are invalid, and there are also no filters + specified in this route rule, *all* traffic which matches this rule MUST + receive an `UNAVAILABLE` status. + + See the GRPCBackendRef definition for the rules about what makes a single + GRPCBackendRef invalid. + + When a GRPCBackendRef is invalid, `UNAVAILABLE` statuses MUST be returned for + requests that would have otherwise been routed to an invalid backend. If + multiple backends are specified, and some are invalid, the proportion of + requests that would otherwise have been routed to an invalid backend + MUST receive an `UNAVAILABLE` status. + + For example, if two backends are specified with equal weights, and one is + invalid, 50 percent of traffic MUST receive an `UNAVAILABLE` status. + Implementations may choose how that 50 percent is determined. + + Support: Core for Kubernetes Service + + Support: Implementation-specific for any other resource + + Support for weight: Core + items: + description: |- + GRPCBackendRef defines how a GRPCRoute forwards a gRPC request. + + Note that when a namespace different than the local namespace is specified, a + ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + + When the BackendRef points to a Kubernetes Service, implementations SHOULD + honor the appProtocol field if it is set for the target Service Port. + + Implementations supporting appProtocol SHOULD recognize the Kubernetes + Standard Application Protocols defined in KEP-3726. + + If a Service appProtocol isn't specified, an implementation MAY infer the + backend protocol through its own means. Implementations MAY infer the + protocol from the Route type referring to the backend Service. + + If a Route is not able to send traffic to the backend using the specified + protocol then the backend is considered invalid. Implementations MUST set the + "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. + properties: + filters: + description: |- + Filters defined at this level MUST be executed if and only if the + request is being forwarded to the backend defined here. + + Support: Implementation-specific (For broader support of filters, use the + Filters field in GRPCRouteRule.) + items: + description: |- + GRPCRouteFilter defines processing steps that must be completed during the + request or response lifecycle. GRPCRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + Support: Implementation-specific + + This filter can be used multiple times within the same rule. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal + to denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be + specified in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations supporting GRPCRoute MUST support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` MUST be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + enum: + - ResponseHeaderModifier + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil + if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type + != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type + == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil + if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type + != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for + RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == + ''RequestMirror'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for + ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + filters: + description: |- + Filters define the filters that are applied to requests that match + this rule. + + The effects of ordering of multiple behaviors are currently unspecified. + This can change in the future based on feedback during the alpha stage. + + Conformance-levels at this level are defined based on the type of filter: + + - ALL core filters MUST be supported by all implementations that support + GRPCRoute. + - Implementers are encouraged to support extended filters. + - Implementation-specific custom filters have no API guarantees across + implementations. + + Specifying the same filter multiple times is not supported unless explicitly + indicated in the filter. + + If an implementation cannot support a combination of filters, it must clearly + document that limitation. In cases where incompatible or unsupported + filters are specified and cause the `Accepted` condition to be set to status + `False`, implementations may use the `IncompatibleFilters` reason to specify + this configuration error. + + Support: Core + items: + description: |- + GRPCRouteFilter defines processing steps that must be completed during the + request or response lifecycle. GRPCRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + Support: Implementation-specific + + This filter can be used multiple times within the same rule. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal to + denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be specified + in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations supporting GRPCRoute MUST support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` MUST be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + enum: + - ResponseHeaderModifier + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the + filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != + ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == + ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the + filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != + ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror + filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef + filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + matches: + description: |- + Matches define conditions used for matching the rule against incoming + gRPC requests. Each match is independent, i.e. this rule will be matched + if **any** one of the matches is satisfied. + + For example, take the following matches configuration: + + ``` + matches: + - method: + service: foo.bar + headers: + values: + version: 2 + - method: + service: foo.bar.v2 + ``` + + For a request to match against this rule, it MUST satisfy + EITHER of the two conditions: + + - service of foo.bar AND contains the header `version: 2` + - service of foo.bar.v2 + + See the documentation for GRPCRouteMatch on how to specify multiple + match conditions to be ANDed together. + + If no matches are specified, the implementation MUST match every gRPC request. + + Proxy or Load Balancer routing configuration generated from GRPCRoutes + MUST prioritize rules based on the following criteria, continuing on + ties. Merging MUST not be done between GRPCRoutes and HTTPRoutes. + Precedence MUST be given to the rule with the largest number of: + + * Characters in a matching non-wildcard hostname. + * Characters in a matching hostname. + * Characters in a matching service. + * Characters in a matching method. + * Header matches. + + If ties still exist across multiple Routes, matching precedence MUST be + determined in order of the following criteria, continuing on ties: + + * The oldest Route based on creation timestamp. + * The Route appearing first in alphabetical order by + "{namespace}/{name}". + + If ties still exist within the Route that has been given precedence, + matching precedence MUST be granted to the first matching rule meeting + the above criteria. + items: + description: |- + GRPCRouteMatch defines the predicate used to match requests to a given + action. Multiple match types are ANDed together, i.e. the match will + evaluate to true only if all conditions are satisfied. + + For example, the match below will match a gRPC request only if its service + is `foo` AND it contains the `version: v1` header: + + ``` + matches: + - method: + type: Exact + service: "foo" + headers: + - name: "version" + value "v1" + + ``` + properties: + headers: + description: |- + Headers specifies gRPC request header matchers. Multiple match values are + ANDed together, meaning, a request MUST match all the specified headers + to select the route. + items: + description: |- + GRPCHeaderMatch describes how to select a gRPC route by matching gRPC request + headers. + properties: + name: + description: |- + Name is the name of the gRPC Header to be matched. + + If multiple entries specify equivalent header names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: Type specifies how to match against + the value of the header. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of the gRPC Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: |- + Method specifies a gRPC request service/method matcher. If this field is + not specified, all services and methods will match. + properties: + method: + description: |- + Value of the method to match against. If left empty or omitted, will + match all services. + + At least one of Service and Method MUST be a non-empty string. + maxLength: 1024 + type: string + service: + description: |- + Value of the service to match against. If left empty or omitted, will + match any service. + + At least one of Service and Method MUST be a non-empty string. + maxLength: 1024 + type: string + type: + default: Exact + description: |- + Type specifies how to match against the service and/or method. + Support: Core (Exact with service and method specified) + + Support: Implementation-specific (Exact with method specified but no service specified) + + Support: Implementation-specific (RegularExpression) + enum: + - Exact + - RegularExpression + type: string + type: object + x-kubernetes-validations: + - message: One or both of 'service' or 'method' must be + specified + rule: 'has(self.type) ? has(self.service) || has(self.method) + : true' + - message: service must only contain valid characters + (matching ^(?i)\.?[a-z_][a-z_0-9]*(\.[a-z_][a-z_0-9]*)*$) + rule: '(!has(self.type) || self.type == ''Exact'') && + has(self.service) ? self.service.matches(r"""^(?i)\.?[a-z_][a-z_0-9]*(\.[a-z_][a-z_0-9]*)*$"""): + true' + - message: method must only contain valid characters (matching + ^[A-Za-z_][A-Za-z_0-9]*$) + rule: '(!has(self.type) || self.type == ''Exact'') && + has(self.method) ? self.method.matches(r"""^[A-Za-z_][A-Za-z_0-9]*$"""): + true' + type: object + maxItems: 64 + type: array + name: + description: |- + Name is the name of the route rule. This name MUST be unique within a Route if it is set. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + sessionPersistence: + description: |- + SessionPersistence defines and configures session persistence + for the route rule. + + Support: Extended + properties: + absoluteTimeout: + description: |- + AbsoluteTimeout defines the absolute timeout of the persistent + session. Once the AbsoluteTimeout duration has elapsed, the + session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + cookieConfig: + description: |- + CookieConfig provides configuration settings that are specific + to cookie-based session persistence. + + Support: Core + properties: + lifetimeType: + default: Session + description: |- + LifetimeType specifies whether the cookie has a permanent or + session-based lifetime. A permanent cookie persists until its + specified expiry time, defined by the Expires or Max-Age cookie + attributes, while a session cookie is deleted when the current + session ends. + + When set to "Permanent", AbsoluteTimeout indicates the + cookie's lifetime via the Expires or Max-Age cookie attributes + and is required. + + When set to "Session", AbsoluteTimeout indicates the + absolute lifetime of the cookie tracked by the gateway and + is optional. + + Defaults to "Session". + + Support: Core for "Session" type + + Support: Extended for "Permanent" type + enum: + - Permanent + - Session + type: string + type: object + idleTimeout: + description: |- + IdleTimeout defines the idle timeout of the persistent session. + Once the session has been idle for more than the specified + IdleTimeout duration, the session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + sessionName: + description: |- + SessionName defines the name of the persistent session token + which may be reflected in the cookie or the header. Users + should avoid reusing session names to prevent unintended + consequences, such as rejection or unpredictable behavior. + + Support: Implementation-specific + maxLength: 128 + type: string + type: + default: Cookie + description: |- + Type defines the type of session persistence such as through + the use a header or cookie. Defaults to cookie based session + persistence. + + Support: Core for "Cookie" type + + Support: Extended for "Header" type + enum: + - Cookie + - Header + type: string + type: object + x-kubernetes-validations: + - message: AbsoluteTimeout must be specified when cookie lifetimeType + is Permanent + rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType) + || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)' + type: object + maxItems: 16 + type: array + x-kubernetes-validations: + - message: While 16 rules and 64 matches per rule are allowed, the + total number of matches across all rules in a route must be less + than 128 + rule: '(self.size() > 0 ? (has(self[0].matches) ? self[0].matches.size() + : 0) : 0) + (self.size() > 1 ? (has(self[1].matches) ? self[1].matches.size() + : 0) : 0) + (self.size() > 2 ? (has(self[2].matches) ? self[2].matches.size() + : 0) : 0) + (self.size() > 3 ? (has(self[3].matches) ? self[3].matches.size() + : 0) : 0) + (self.size() > 4 ? (has(self[4].matches) ? self[4].matches.size() + : 0) : 0) + (self.size() > 5 ? (has(self[5].matches) ? self[5].matches.size() + : 0) : 0) + (self.size() > 6 ? (has(self[6].matches) ? self[6].matches.size() + : 0) : 0) + (self.size() > 7 ? (has(self[7].matches) ? self[7].matches.size() + : 0) : 0) + (self.size() > 8 ? (has(self[8].matches) ? self[8].matches.size() + : 0) : 0) + (self.size() > 9 ? (has(self[9].matches) ? self[9].matches.size() + : 0) : 0) + (self.size() > 10 ? (has(self[10].matches) ? self[10].matches.size() + : 0) : 0) + (self.size() > 11 ? (has(self[11].matches) ? self[11].matches.size() + : 0) : 0) + (self.size() > 12 ? (has(self[12].matches) ? self[12].matches.size() + : 0) : 0) + (self.size() > 13 ? (has(self[13].matches) ? self[13].matches.size() + : 0) : 0) + (self.size() > 14 ? (has(self[14].matches) ? self[14].matches.size() + : 0) : 0) + (self.size() > 15 ? (has(self[15].matches) ? self[15].matches.size() + : 0) : 0) <= 128' + - message: Rule name must be unique within the route + rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) + && l1.name == l2.name)) + type: object + status: + description: Status defines the current state of GRPCRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_httproutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: httproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: HTTPRoute + listKind: HTTPRouteList + plural: httproutes + singular: httproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + HTTPRoute provides a way to route HTTP requests. This includes the capability + to match requests by hostname, path, header, or query param. Filters can be + used to specify additional processing steps. Backends specify where matching + requests should be routed. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of HTTPRoute. + properties: + hostnames: + description: |- + Hostnames defines a set of hostnames that should match against the HTTP Host + header to select a HTTPRoute used to process the request. Implementations + MUST ignore any port value specified in the HTTP Host header while + performing a match and (absent of any applicable header modification + configuration) MUST forward this header unmodified to the backend. + + Valid values for Hostnames are determined by RFC 1123 definition of a + hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + If a hostname is specified by both the Listener and HTTPRoute, there + must be at least one intersecting hostname for the HTTPRoute to be + attached to the Listener. For example: + + * A Listener with `test.example.com` as the hostname matches HTTPRoutes + that have either not specified any hostnames, or have specified at + least one of `test.example.com` or `*.example.com`. + * A Listener with `*.example.com` as the hostname matches HTTPRoutes + that have either not specified any hostnames or have specified at least + one hostname that matches the Listener hostname. For example, + `*.example.com`, `test.example.com`, and `foo.test.example.com` would + all match. On the other hand, `example.com` and `test.example.net` would + not match. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + + If both the Listener and HTTPRoute have specified hostnames, any + HTTPRoute hostnames that do not match the Listener hostname MUST be + ignored. For example, if a Listener specified `*.example.com`, and the + HTTPRoute specified `test.example.com` and `test.example.net`, + `test.example.net` must not be considered for a match. + + If both the Listener and HTTPRoute have specified hostnames, and none + match with the criteria above, then the HTTPRoute is not accepted. The + implementation must raise an 'Accepted' Condition with a status of + `False` in the corresponding RouteParentStatus. + + In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. + overlapping wildcard matching and exact matching hostnames), precedence must + be given to rules from the HTTPRoute with the largest number of: + + * Characters in a matching non-wildcard hostname. + * Characters in a matching hostname. + + If ties exist across multiple Routes, the matching precedence rules for + HTTPRouteMatches takes over. + + Support: Core + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) + rules: + default: + - matches: + - path: + type: PathPrefix + value: / + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: |- + HTTPRouteRule defines semantics for matching an HTTP request based on + conditions (matches), processing it (filters), and forwarding the request to + an API object (backendRefs). + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. + + Failure behavior here depends on how many BackendRefs are specified and + how many are invalid. + + If *all* entries in BackendRefs are invalid, and there are also no filters + specified in this route rule, *all* traffic which matches this rule MUST + receive a 500 status code. + + See the HTTPBackendRef definition for the rules about what makes a single + HTTPBackendRef invalid. + + When a HTTPBackendRef is invalid, 500 status codes MUST be returned for + requests that would have otherwise been routed to an invalid backend. If + multiple backends are specified, and some are invalid, the proportion of + requests that would otherwise have been routed to an invalid backend + MUST receive a 500 status code. + + For example, if two backends are specified with equal weights, and one is + invalid, 50 percent of traffic must receive a 500. Implementations may + choose how that 50 percent is determined. + + When a HTTPBackendRef refers to a Service that has no ready endpoints, + implementations SHOULD return a 503 for requests to that backend instead. + If an implementation chooses to do this, all of the above rules for 500 responses + MUST also apply for responses that return a 503. + + Support: Core for Kubernetes Service + + Support: Extended for Kubernetes ServiceImport + + Support: Implementation-specific for any other resource + + Support for weight: Core + items: + description: |- + HTTPBackendRef defines how a HTTPRoute forwards a HTTP request. + + Note that when a namespace different than the local namespace is specified, a + ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + + When the BackendRef points to a Kubernetes Service, implementations SHOULD + honor the appProtocol field if it is set for the target Service Port. + + Implementations supporting appProtocol SHOULD recognize the Kubernetes + Standard Application Protocols defined in KEP-3726. + + If a Service appProtocol isn't specified, an implementation MAY infer the + backend protocol through its own means. Implementations MAY infer the + protocol from the Route type referring to the backend Service. + + If a Route is not able to send traffic to the backend using the specified + protocol then the backend is considered invalid. Implementations MUST set the + "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. + properties: + filters: + description: |- + Filters defined at this level should be executed if and only if the + request is being forwarded to the backend defined here. + + Support: Implementation-specific (For broader support of filters, use the + Filters field in HTTPRouteRule.) + items: + description: |- + HTTPRouteFilter defines processing steps that must be completed during the + request or response lifecycle. HTTPRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + cors: + description: |- + CORS defines a schema for a filter that responds to the + cross-origin request based on HTTP response header. + + Support: Extended + properties: + allowCredentials: + description: |- + AllowCredentials indicates whether the actual cross-origin request allows + to include credentials. + + The only valid value for the `Access-Control-Allow-Credentials` response + header is true (case-sensitive). + + If the credentials are not allowed in cross-origin requests, the gateway + will omit the header `Access-Control-Allow-Credentials` entirely rather + than setting its value to false. + + Support: Extended + enum: + - true + type: boolean + allowHeaders: + description: |- + AllowHeaders indicates which HTTP request headers are supported for + accessing the requested resource. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Allow-Headers` + response header are separated by a comma (","). + + When the `AllowHeaders` field is configured with one or more headers, the + gateway must return the `Access-Control-Allow-Headers` response header + which value is present in the `AllowHeaders` field. + + If any header name in the `Access-Control-Request-Headers` request header + is not included in the list of header names specified by the response + header `Access-Control-Allow-Headers`, it will present an error on the + client side. + + If any header name in the `Access-Control-Allow-Headers` response header + does not recognize by the client, it will also occur an error on the + client side. + + A wildcard indicates that the requests with all HTTP headers are allowed. + The `Access-Control-Allow-Headers` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowHeaders` field + specified with the `*` wildcard, the gateway must specify one or more + HTTP headers in the value of the `Access-Control-Allow-Headers` response + header. The value of the header `Access-Control-Allow-Headers` is same as + the `Access-Control-Request-Headers` header provided by the client. If + the header `Access-Control-Request-Headers` is not included in the + request, the gateway will omit the `Access-Control-Allow-Headers` + response header, instead of specifying the `*` wildcard. A Gateway + implementation may choose to add implementation-specific default headers. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + allowMethods: + description: |- + AllowMethods indicates which HTTP methods are supported for accessing the + requested resource. + + Valid values are any method defined by RFC9110, along with the special + value `*`, which represents all HTTP methods are allowed. + + Method names are case sensitive, so these values are also case-sensitive. + (See https://www.rfc-editor.org/rfc/rfc2616#section-5.1.1) + + Multiple method names in the value of the `Access-Control-Allow-Methods` + response header are separated by a comma (","). + + A CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`. + (See https://fetch.spec.whatwg.org/#cors-safelisted-method) The + CORS-safelisted methods are always allowed, regardless of whether they + are specified in the `AllowMethods` field. + + When the `AllowMethods` field is configured with one or more methods, the + gateway must return the `Access-Control-Allow-Methods` response header + which value is present in the `AllowMethods` field. + + If the HTTP method of the `Access-Control-Request-Method` request header + is not included in the list of methods specified by the response header + `Access-Control-Allow-Methods`, it will present an error on the client + side. + + The `Access-Control-Allow-Methods` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowMethods` field + specified with the `*` wildcard, the gateway must specify one HTTP method + in the value of the Access-Control-Allow-Methods response header. The + value of the header `Access-Control-Allow-Methods` is same as the + `Access-Control-Request-Method` header provided by the client. If the + header `Access-Control-Request-Method` is not included in the request, + the gateway will omit the `Access-Control-Allow-Methods` response header, + instead of specifying the `*` wildcard. A Gateway implementation may + choose to add implementation-specific default methods. + + Support: Extended + items: + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + - '*' + type: string + maxItems: 9 + type: array + x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowMethods cannot contain '*' alongside + other methods + rule: '!(''*'' in self && self.size() > 1)' + allowOrigins: + description: |- + AllowOrigins indicates whether the response can be shared with requested + resource from the given `Origin`. + + The `Origin` consists of a scheme and a host, with an optional port, and + takes the form `://(:)`. + + Valid values for scheme are: `http` and `https`. + + Valid values for port are any integer between 1 and 65535 (the list of + available TCP/UDP ports). Note that, if not included, port `80` is + assumed for `http` scheme origins, and port `443` is assumed for `https` + origins. This may affect origin matching. + + The host part of the origin may contain the wildcard character `*`. These + wildcard characters behave as follows: + + * `*` is a greedy match to the _left_, including any number of + DNS labels to the left of its position. This also means that + `*` will include any number of period `.` characters to the + left of its position. + * A wildcard by itself matches all hosts. + + An origin value that includes _only_ the `*` character indicates requests + from all `Origin`s are allowed. + + When the `AllowOrigins` field is configured with multiple origins, it + means the server supports clients from multiple origins. If the request + `Origin` matches the configured allowed origins, the gateway must return + the given `Origin` and sets value of the header + `Access-Control-Allow-Origin` same as the `Origin` header provided by the + client. + + The status code of a successful response to a "preflight" request is + always an OK status (i.e., 204 or 200). + + If the request `Origin` does not match the configured allowed origins, + the gateway returns 204/200 response but doesn't set the relevant + cross-origin response headers. Alternatively, the gateway responds with + 403 status to the "preflight" request is denied, coupled with omitting + the CORS headers. The cross-origin request fails on the client side. + Therefore, the client doesn't attempt the actual cross-origin request. + + The `Access-Control-Allow-Origin` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowOrigins` field + specified with the `*` wildcard, the gateway must return a single origin + in the value of the `Access-Control-Allow-Origin` response header, + instead of specifying the `*` wildcard. The value of the header + `Access-Control-Allow-Origin` is same as the `Origin` header provided by + the client. + + Support: Extended + items: + description: |- + The AbsoluteURI MUST NOT be a relative URI, and it MUST follow the URI syntax and + encoding rules specified in RFC3986. The AbsoluteURI MUST include both a + scheme (e.g., "http" or "spiffe") and a scheme-specific-part. URIs that + include an authority MUST include a fully qualified domain name or + IP address as the host. + maxLength: 253 + minLength: 1 + pattern: ^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))? + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + exposeHeaders: + description: |- + ExposeHeaders indicates which HTTP response headers can be exposed + to client-side scripts in response to a cross-origin request. + + A CORS-safelisted response header is an HTTP header in a CORS response + that it is considered safe to expose to the client scripts. + The CORS-safelisted response headers include the following headers: + `Cache-Control` + `Content-Language` + `Content-Length` + `Content-Type` + `Expires` + `Last-Modified` + `Pragma` + (See https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name) + The CORS-safelisted response headers are exposed to client by default. + + When an HTTP header name is specified using the `ExposeHeaders` field, + this additional header will be exposed as part of the response to the + client. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Expose-Headers` + response header are separated by a comma (","). + + A wildcard indicates that the responses with all HTTP headers are exposed + to clients. The `Access-Control-Expose-Headers` response header can only + use `*` wildcard as value when the `AllowCredentials` field is + unspecified. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + maxAge: + default: 5 + description: |- + MaxAge indicates the duration (in seconds) for the client to cache the + results of a "preflight" request. + + The information provided by the `Access-Control-Allow-Methods` and + `Access-Control-Allow-Headers` response headers can be cached by the + client until the time specified by `Access-Control-Max-Age` elapses. + + The default value of `Access-Control-Max-Age` response header is 5 + (seconds). + format: int32 + minimum: 1 + type: integer + type: object + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + This filter can be used multiple times within the same rule. + + Support: Implementation-specific + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal + to denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be + specified in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + requestRedirect: + description: |- + RequestRedirect defines a schema for a filter that responds to the + request with an HTTP redirection. + + Support: Core + properties: + hostname: + description: |- + Hostname is the hostname to be used in the value of the `Location` + header in the response. + When empty, the hostname in the `Host` header of the request is used. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines parameters used to modify the path of the incoming request. + The modified path is then used to construct the `Location` header. When + empty, the request path is used as-is. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified + when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? + has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when + replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type + == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified + when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' + ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' + when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + port: + description: |- + Port is the port to be used in the value of the `Location` + header in the response. + + If no port is specified, the redirect port MUST be derived using the + following rules: + + * If redirect scheme is not-empty, the redirect port MUST be the well-known + port associated with the redirect scheme. Specifically "http" to port 80 + and "https" to port 443. If the redirect scheme does not have a + well-known port, the listener port of the Gateway SHOULD be used. + * If redirect scheme is empty, the redirect port MUST be the Gateway + Listener port. + + Implementations SHOULD NOT add the port number in the 'Location' + header in the following cases: + + * A Location header that will use HTTP (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 80. + * A Location header that will use HTTPS (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 443. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: |- + Scheme is the scheme to be used in the value of the `Location` header in + the response. When empty, the scheme of the request is used. + + Scheme redirects can affect the port of the redirect, for more information, + refer to the documentation for the port field of this filter. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Extended + enum: + - http + - https + type: string + statusCode: + default: 302 + description: |- + StatusCode is the HTTP status code to be used in response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Core + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations must support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by + specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` should be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + - CORS + type: string + urlRewrite: + description: |- + URLRewrite defines a schema for a filter that modifies a request during forwarding. + + Support: Extended + properties: + hostname: + description: |- + Hostname is the value to be used to replace the Host header value during + forwarding. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines a path rewrite. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified + when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? + has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when + replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type + == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified + when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' + ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' + when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil + if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type + != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type + == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil + if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type + != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for + RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == + ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the + filter.type is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != + ''RequestRedirect'')' + - message: filter.requestRedirect must be specified + for RequestRedirect filter.type + rule: '!(!has(self.requestRedirect) && self.type == + ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type + is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite + filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for + ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + - message: filter.cors must be nil if the filter.type + is not CORS + rule: '!(has(self.cors) && self.type != ''CORS'')' + - message: filter.cors must be specified for CORS filter.type + rule: '!(!has(self.cors) && self.type == ''CORS'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') + && self.exists(f, f.type == ''URLRewrite''))' + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') + && self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() + <= 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() + <= 1 + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + filters: + description: |- + Filters define the filters that are applied to requests that match + this rule. + + Wherever possible, implementations SHOULD implement filters in the order + they are specified. + + Implementations MAY choose to implement this ordering strictly, rejecting + any combination or order of filters that cannot be supported. If implementations + choose a strict interpretation of filter ordering, they MUST clearly document + that behavior. + + To reject an invalid combination or order of filters, implementations SHOULD + consider the Route Rules with this configuration invalid. If all Route Rules + in a Route are invalid, the entire Route would be considered invalid. If only + a portion of Route Rules are invalid, implementations MUST set the + "PartiallyInvalid" condition for the Route. + + Conformance-levels at this level are defined based on the type of filter: + + - ALL core filters MUST be supported by all implementations. + - Implementers are encouraged to support extended filters. + - Implementation-specific custom filters have no API guarantees across + implementations. + + Specifying the same filter multiple times is not supported unless explicitly + indicated in the filter. + + All filters are expected to be compatible with each other except for the + URLRewrite and RequestRedirect filters, which may not be combined. If an + implementation cannot support other combinations of filters, they must clearly + document that limitation. In cases where incompatible or unsupported + filters are specified and cause the `Accepted` condition to be set to status + `False`, implementations may use the `IncompatibleFilters` reason to specify + this configuration error. + + Support: Core + items: + description: |- + HTTPRouteFilter defines processing steps that must be completed during the + request or response lifecycle. HTTPRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + cors: + description: |- + CORS defines a schema for a filter that responds to the + cross-origin request based on HTTP response header. + + Support: Extended + properties: + allowCredentials: + description: |- + AllowCredentials indicates whether the actual cross-origin request allows + to include credentials. + + The only valid value for the `Access-Control-Allow-Credentials` response + header is true (case-sensitive). + + If the credentials are not allowed in cross-origin requests, the gateway + will omit the header `Access-Control-Allow-Credentials` entirely rather + than setting its value to false. + + Support: Extended + enum: + - true + type: boolean + allowHeaders: + description: |- + AllowHeaders indicates which HTTP request headers are supported for + accessing the requested resource. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Allow-Headers` + response header are separated by a comma (","). + + When the `AllowHeaders` field is configured with one or more headers, the + gateway must return the `Access-Control-Allow-Headers` response header + which value is present in the `AllowHeaders` field. + + If any header name in the `Access-Control-Request-Headers` request header + is not included in the list of header names specified by the response + header `Access-Control-Allow-Headers`, it will present an error on the + client side. + + If any header name in the `Access-Control-Allow-Headers` response header + does not recognize by the client, it will also occur an error on the + client side. + + A wildcard indicates that the requests with all HTTP headers are allowed. + The `Access-Control-Allow-Headers` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowHeaders` field + specified with the `*` wildcard, the gateway must specify one or more + HTTP headers in the value of the `Access-Control-Allow-Headers` response + header. The value of the header `Access-Control-Allow-Headers` is same as + the `Access-Control-Request-Headers` header provided by the client. If + the header `Access-Control-Request-Headers` is not included in the + request, the gateway will omit the `Access-Control-Allow-Headers` + response header, instead of specifying the `*` wildcard. A Gateway + implementation may choose to add implementation-specific default headers. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + allowMethods: + description: |- + AllowMethods indicates which HTTP methods are supported for accessing the + requested resource. + + Valid values are any method defined by RFC9110, along with the special + value `*`, which represents all HTTP methods are allowed. + + Method names are case sensitive, so these values are also case-sensitive. + (See https://www.rfc-editor.org/rfc/rfc2616#section-5.1.1) + + Multiple method names in the value of the `Access-Control-Allow-Methods` + response header are separated by a comma (","). + + A CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`. + (See https://fetch.spec.whatwg.org/#cors-safelisted-method) The + CORS-safelisted methods are always allowed, regardless of whether they + are specified in the `AllowMethods` field. + + When the `AllowMethods` field is configured with one or more methods, the + gateway must return the `Access-Control-Allow-Methods` response header + which value is present in the `AllowMethods` field. + + If the HTTP method of the `Access-Control-Request-Method` request header + is not included in the list of methods specified by the response header + `Access-Control-Allow-Methods`, it will present an error on the client + side. + + The `Access-Control-Allow-Methods` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowMethods` field + specified with the `*` wildcard, the gateway must specify one HTTP method + in the value of the Access-Control-Allow-Methods response header. The + value of the header `Access-Control-Allow-Methods` is same as the + `Access-Control-Request-Method` header provided by the client. If the + header `Access-Control-Request-Method` is not included in the request, + the gateway will omit the `Access-Control-Allow-Methods` response header, + instead of specifying the `*` wildcard. A Gateway implementation may + choose to add implementation-specific default methods. + + Support: Extended + items: + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + - '*' + type: string + maxItems: 9 + type: array + x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowMethods cannot contain '*' alongside + other methods + rule: '!(''*'' in self && self.size() > 1)' + allowOrigins: + description: |- + AllowOrigins indicates whether the response can be shared with requested + resource from the given `Origin`. + + The `Origin` consists of a scheme and a host, with an optional port, and + takes the form `://(:)`. + + Valid values for scheme are: `http` and `https`. + + Valid values for port are any integer between 1 and 65535 (the list of + available TCP/UDP ports). Note that, if not included, port `80` is + assumed for `http` scheme origins, and port `443` is assumed for `https` + origins. This may affect origin matching. + + The host part of the origin may contain the wildcard character `*`. These + wildcard characters behave as follows: + + * `*` is a greedy match to the _left_, including any number of + DNS labels to the left of its position. This also means that + `*` will include any number of period `.` characters to the + left of its position. + * A wildcard by itself matches all hosts. + + An origin value that includes _only_ the `*` character indicates requests + from all `Origin`s are allowed. + + When the `AllowOrigins` field is configured with multiple origins, it + means the server supports clients from multiple origins. If the request + `Origin` matches the configured allowed origins, the gateway must return + the given `Origin` and sets value of the header + `Access-Control-Allow-Origin` same as the `Origin` header provided by the + client. + + The status code of a successful response to a "preflight" request is + always an OK status (i.e., 204 or 200). + + If the request `Origin` does not match the configured allowed origins, + the gateway returns 204/200 response but doesn't set the relevant + cross-origin response headers. Alternatively, the gateway responds with + 403 status to the "preflight" request is denied, coupled with omitting + the CORS headers. The cross-origin request fails on the client side. + Therefore, the client doesn't attempt the actual cross-origin request. + + The `Access-Control-Allow-Origin` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowOrigins` field + specified with the `*` wildcard, the gateway must return a single origin + in the value of the `Access-Control-Allow-Origin` response header, + instead of specifying the `*` wildcard. The value of the header + `Access-Control-Allow-Origin` is same as the `Origin` header provided by + the client. + + Support: Extended + items: + description: |- + The AbsoluteURI MUST NOT be a relative URI, and it MUST follow the URI syntax and + encoding rules specified in RFC3986. The AbsoluteURI MUST include both a + scheme (e.g., "http" or "spiffe") and a scheme-specific-part. URIs that + include an authority MUST include a fully qualified domain name or + IP address as the host. + maxLength: 253 + minLength: 1 + pattern: ^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))? + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + exposeHeaders: + description: |- + ExposeHeaders indicates which HTTP response headers can be exposed + to client-side scripts in response to a cross-origin request. + + A CORS-safelisted response header is an HTTP header in a CORS response + that it is considered safe to expose to the client scripts. + The CORS-safelisted response headers include the following headers: + `Cache-Control` + `Content-Language` + `Content-Length` + `Content-Type` + `Expires` + `Last-Modified` + `Pragma` + (See https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name) + The CORS-safelisted response headers are exposed to client by default. + + When an HTTP header name is specified using the `ExposeHeaders` field, + this additional header will be exposed as part of the response to the + client. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Expose-Headers` + response header are separated by a comma (","). + + A wildcard indicates that the responses with all HTTP headers are exposed + to clients. The `Access-Control-Expose-Headers` response header can only + use `*` wildcard as value when the `AllowCredentials` field is + unspecified. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + maxAge: + default: 5 + description: |- + MaxAge indicates the duration (in seconds) for the client to cache the + results of a "preflight" request. + + The information provided by the `Access-Control-Allow-Methods` and + `Access-Control-Allow-Headers` response headers can be cached by the + client until the time specified by `Access-Control-Max-Age` elapses. + + The default value of `Access-Control-Max-Age` response header is 5 + (seconds). + format: int32 + minimum: 1 + type: integer + type: object + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + This filter can be used multiple times within the same rule. + + Support: Implementation-specific + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal to + denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be specified + in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + requestRedirect: + description: |- + RequestRedirect defines a schema for a filter that responds to the + request with an HTTP redirection. + + Support: Core + properties: + hostname: + description: |- + Hostname is the hostname to be used in the value of the `Location` + header in the response. + When empty, the hostname in the `Host` header of the request is used. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines parameters used to modify the path of the incoming request. + The modified path is then used to construct the `Location` header. When + empty, the request path is used as-is. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when + type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) + : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath + is set + rule: 'has(self.replaceFullPath) ? self.type == + ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when + type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) + : true' + - message: type must be 'ReplacePrefixMatch' when + replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + port: + description: |- + Port is the port to be used in the value of the `Location` + header in the response. + + If no port is specified, the redirect port MUST be derived using the + following rules: + + * If redirect scheme is not-empty, the redirect port MUST be the well-known + port associated with the redirect scheme. Specifically "http" to port 80 + and "https" to port 443. If the redirect scheme does not have a + well-known port, the listener port of the Gateway SHOULD be used. + * If redirect scheme is empty, the redirect port MUST be the Gateway + Listener port. + + Implementations SHOULD NOT add the port number in the 'Location' + header in the following cases: + + * A Location header that will use HTTP (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 80. + * A Location header that will use HTTPS (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 443. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: |- + Scheme is the scheme to be used in the value of the `Location` header in + the response. When empty, the scheme of the request is used. + + Scheme redirects can affect the port of the redirect, for more information, + refer to the documentation for the port field of this filter. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Extended + enum: + - http + - https + type: string + statusCode: + default: 302 + description: |- + StatusCode is the HTTP status code to be used in response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Core + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations must support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by + specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` should be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + - CORS + type: string + urlRewrite: + description: |- + URLRewrite defines a schema for a filter that modifies a request during forwarding. + + Support: Extended + properties: + hostname: + description: |- + Hostname is the value to be used to replace the Host header value during + forwarding. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines a path rewrite. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when + type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) + : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath + is set + rule: 'has(self.replaceFullPath) ? self.type == + ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when + type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) + : true' + - message: type must be 'ReplacePrefixMatch' when + replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the + filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != + ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == + ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the + filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != + ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror + filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the filter.type + is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')' + - message: filter.requestRedirect must be specified for RequestRedirect + filter.type + rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type + is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite + filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef + filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + - message: filter.cors must be nil if the filter.type is not + CORS + rule: '!(has(self.cors) && self.type != ''CORS'')' + - message: filter.cors must be specified for CORS filter.type + rule: '!(!has(self.cors) && self.type == ''CORS'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') && + self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() <= + 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() <= 1 + matches: + default: + - path: + type: PathPrefix + value: / + description: |- + Matches define conditions used for matching the rule against incoming + HTTP requests. Each match is independent, i.e. this rule will be matched + if **any** one of the matches is satisfied. + + For example, take the following matches configuration: + + ``` + matches: + - path: + value: "/foo" + headers: + - name: "version" + value: "v2" + - path: + value: "/v2/foo" + ``` + + For a request to match against this rule, a request must satisfy + EITHER of the two conditions: + + - path prefixed with `/foo` AND contains the header `version: v2` + - path prefix of `/v2/foo` + + See the documentation for HTTPRouteMatch on how to specify multiple + match conditions that should be ANDed together. + + If no matches are specified, the default is a prefix + path match on "/", which has the effect of matching every + HTTP request. + + Proxy or Load Balancer routing configuration generated from HTTPRoutes + MUST prioritize matches based on the following criteria, continuing on + ties. Across all rules specified on applicable Routes, precedence must be + given to the match having: + + * "Exact" path match. + * "Prefix" path match with largest number of characters. + * Method match. + * Largest number of header matches. + * Largest number of query param matches. + + Note: The precedence of RegularExpression path matches are implementation-specific. + + If ties still exist across multiple Routes, matching precedence MUST be + determined in order of the following criteria, continuing on ties: + + * The oldest Route based on creation timestamp. + * The Route appearing first in alphabetical order by + "{namespace}/{name}". + + If ties still exist within an HTTPRoute, matching precedence MUST be granted + to the FIRST matching rule (in list order) with a match meeting the above + criteria. + + When no rules matching a request have been successfully attached to the + parent a request is coming from, a HTTP 404 status code MUST be returned. + items: + description: "HTTPRouteMatch defines the predicate used to + match requests to a given\naction. Multiple match types + are ANDed together, i.e. the match will\nevaluate to true + only if all conditions are satisfied.\n\nFor example, the + match below will match a HTTP request only if its path\nstarts + with `/foo` AND it contains the `version: v1` header:\n\n```\nmatch:\n\n\tpath:\n\t + \ value: \"/foo\"\n\theaders:\n\t- name: \"version\"\n\t + \ value \"v1\"\n\n```" + properties: + headers: + description: |- + Headers specifies HTTP request header matchers. Multiple match values are + ANDed together, meaning, a request must match all the specified headers + to select the route. + items: + description: |- + HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request + headers. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + + When a header is repeated in an HTTP request, it is + implementation-specific behavior as to how this is represented. + Generally, proxies should follow the guidance from the RFC: + https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding + processing a repeated header, with special handling for "Set-Cookie". + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the header. + + Support: Core (Exact) + + Support: Implementation-specific (RegularExpression) + + Since RegularExpression HeaderMatchType has implementation-specific + conformance, implementations can support POSIX, PCRE or any other dialects + of regular expressions. Please read the implementation's documentation to + determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP Header to + be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: |- + Method specifies HTTP method matcher. + When specified, this route will be matched only if the request has the + specified method. + + Support: Extended + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: |- + Path specifies a HTTP request path matcher. If this field is not + specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: |- + Type specifies how to match against the path Value. + + Support: Core (Exact, PathPrefix) + + Support: Implementation-specific (RegularExpression) + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path to match against. + maxLength: 1024 + type: string + type: object + x-kubernetes-validations: + - message: value must be an absolute path and start with + '/' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.startsWith(''/'') + : true' + - message: must not contain '//' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''//'') + : true' + - message: must not contain '/./' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/./'') + : true' + - message: must not contain '/../' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/../'') + : true' + - message: must not contain '%2f' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2f'') + : true' + - message: must not contain '%2F' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2F'') + : true' + - message: must not contain '#' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''#'') + : true' + - message: must not end with '/..' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/..'') + : true' + - message: must not end with '/.' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/.'') + : true' + - message: type must be one of ['Exact', 'PathPrefix', + 'RegularExpression'] + rule: self.type in ['Exact','PathPrefix'] || self.type + == 'RegularExpression' + - message: must only contain valid characters (matching + ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$) + for types ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.matches(r"""^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$""") + : true' + queryParams: + description: |- + QueryParams specifies HTTP query parameter matchers. Multiple match + values are ANDed together, meaning, a request must match all the + specified query parameters to select the route. + + Support: Extended + items: + description: |- + HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP + query parameters. + properties: + name: + description: |- + Name is the name of the HTTP query param to be matched. This must be an + exact string match. (See + https://tools.ietf.org/html/rfc7230#section-2.7.3). + + If multiple entries specify equivalent query param names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent query param name MUST be ignored. + + If a query param is repeated in an HTTP request, the behavior is + purposely left undefined, since different data planes have different + capabilities. However, it is *recommended* that implementations should + match against the first value of the param if the data plane supports it, + as this behavior is expected in other load balancing contexts outside of + the Gateway API. + + Users SHOULD NOT route traffic based on repeated query params to guard + themselves against potential differences in the implementations. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the query parameter. + + Support: Extended (Exact) + + Support: Implementation-specific (RegularExpression) + + Since RegularExpression QueryParamMatchType has Implementation-specific + conformance, implementations can support POSIX, PCRE or any other + dialects of regular expressions. Please read the implementation's + documentation to determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP query param + to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 64 + type: array + name: + description: |- + Name is the name of the route rule. This name MUST be unique within a Route if it is set. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + retry: + description: |- + Retry defines the configuration for when to retry an HTTP request. + + Support: Extended + properties: + attempts: + description: |- + Attempts specifies the maximum number of times an individual request + from the gateway to a backend should be retried. + + If the maximum number of retries has been attempted without a successful + response from the backend, the Gateway MUST return an error. + + When this field is unspecified, the number of times to attempt to retry + a backend request is implementation-specific. + + Support: Extended + type: integer + backoff: + description: |- + Backoff specifies the minimum duration a Gateway should wait between + retry attempts and is represented in Gateway API Duration formatting. + + For example, setting the `rules[].retry.backoff` field to the value + `100ms` will cause a backend request to first be retried approximately + 100 milliseconds after timing out or receiving a response code configured + to be retryable. + + An implementation MAY use an exponential or alternative backoff strategy + for subsequent retry attempts, MAY cap the maximum backoff duration to + some amount greater than the specified minimum, and MAY add arbitrary + jitter to stagger requests, as long as unsuccessful backend requests are + not retried before the configured minimum duration. + + If a Request timeout (`rules[].timeouts.request`) is configured on the + route, the entire duration of the initial request and any retry attempts + MUST not exceed the Request timeout duration. If any retry attempts are + still in progress when the Request timeout duration has been reached, + these SHOULD be canceled if possible and the Gateway MUST immediately + return a timeout error. + + If a BackendRequest timeout (`rules[].timeouts.backendRequest`) is + configured on the route, any retry attempts which reach the configured + BackendRequest timeout duration without a response SHOULD be canceled if + possible and the Gateway should wait for at least the specified backoff + duration before attempting to retry the backend request again. + + If a BackendRequest timeout is _not_ configured on the route, retry + attempts MAY time out after an implementation default duration, or MAY + remain pending until a configured Request timeout or implementation + default duration for total request time is reached. + + When this field is unspecified, the time to wait between retry attempts + is implementation-specific. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + codes: + description: |- + Codes defines the HTTP response status codes for which a backend request + should be retried. + + Support: Extended + items: + description: |- + HTTPRouteRetryStatusCode defines an HTTP response status code for + which a backend request should be retried. + + Implementations MUST support the following status codes as retryable: + + * 500 + * 502 + * 503 + * 504 + + Implementations MAY support specifying additional discrete values in the + 500-599 range. + + Implementations MAY support specifying discrete values in the 400-499 range, + which are often inadvisable to retry. + maximum: 599 + minimum: 400 + type: integer + type: array + type: object + sessionPersistence: + description: |- + SessionPersistence defines and configures session persistence + for the route rule. + + Support: Extended + properties: + absoluteTimeout: + description: |- + AbsoluteTimeout defines the absolute timeout of the persistent + session. Once the AbsoluteTimeout duration has elapsed, the + session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + cookieConfig: + description: |- + CookieConfig provides configuration settings that are specific + to cookie-based session persistence. + + Support: Core + properties: + lifetimeType: + default: Session + description: |- + LifetimeType specifies whether the cookie has a permanent or + session-based lifetime. A permanent cookie persists until its + specified expiry time, defined by the Expires or Max-Age cookie + attributes, while a session cookie is deleted when the current + session ends. + + When set to "Permanent", AbsoluteTimeout indicates the + cookie's lifetime via the Expires or Max-Age cookie attributes + and is required. + + When set to "Session", AbsoluteTimeout indicates the + absolute lifetime of the cookie tracked by the gateway and + is optional. + + Defaults to "Session". + + Support: Core for "Session" type + + Support: Extended for "Permanent" type + enum: + - Permanent + - Session + type: string + type: object + idleTimeout: + description: |- + IdleTimeout defines the idle timeout of the persistent session. + Once the session has been idle for more than the specified + IdleTimeout duration, the session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + sessionName: + description: |- + SessionName defines the name of the persistent session token + which may be reflected in the cookie or the header. Users + should avoid reusing session names to prevent unintended + consequences, such as rejection or unpredictable behavior. + + Support: Implementation-specific + maxLength: 128 + type: string + type: + default: Cookie + description: |- + Type defines the type of session persistence such as through + the use a header or cookie. Defaults to cookie based session + persistence. + + Support: Core for "Cookie" type + + Support: Extended for "Header" type + enum: + - Cookie + - Header + type: string + type: object + x-kubernetes-validations: + - message: AbsoluteTimeout must be specified when cookie lifetimeType + is Permanent + rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType) + || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)' + timeouts: + description: |- + Timeouts defines the timeouts that can be configured for an HTTP request. + + Support: Extended + properties: + backendRequest: + description: |- + BackendRequest specifies a timeout for an individual request from the gateway + to a backend. This covers the time from when the request first starts being + sent from the gateway to when the full response has been received from the backend. + + Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout + completely. Implementations that cannot completely disable the timeout MUST + instead interpret the zero duration as the longest possible value to which + the timeout can be set. + + An entire client HTTP transaction with a gateway, covered by the Request timeout, + may result in more than one call from the gateway to the destination backend, + for example, if automatic retries are supported. + + The value of BackendRequest must be a Gateway API Duration string as defined by + GEP-2257. When this field is unspecified, its behavior is implementation-specific; + when specified, the value of BackendRequest must be no more than the value of the + Request timeout (since the Request timeout encompasses the BackendRequest timeout). + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + request: + description: |- + Request specifies the maximum duration for a gateway to respond to an HTTP request. + If the gateway has not been able to respond before this deadline is met, the gateway + MUST return a timeout error. + + For example, setting the `rules.timeouts.request` field to the value `10s` in an + `HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds + to complete. + + Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout + completely. Implementations that cannot completely disable the timeout MUST + instead interpret the zero duration as the longest possible value to which + the timeout can be set. + + This timeout is intended to cover as close to the whole request-response transaction + as possible although an implementation MAY choose to start the timeout after the entire + request stream has been received instead of immediately after the transaction is + initiated by the client. + + The value of Request is a Gateway API Duration string as defined by GEP-2257. When this + field is unspecified, request timeout behavior is implementation-specific. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + x-kubernetes-validations: + - message: backendRequest timeout cannot be longer than request + timeout + rule: '!(has(self.request) && has(self.backendRequest) && + duration(self.request) != duration(''0s'') && duration(self.backendRequest) + > duration(self.request))' + type: object + x-kubernetes-validations: + - message: RequestRedirect filter must not be used together with + backendRefs + rule: '(has(self.backendRefs) && size(self.backendRefs) > 0) ? + (!has(self.filters) || self.filters.all(f, !has(f.requestRedirect))): + true' + - message: When using RequestRedirect filter with path.replacePrefixMatch, + exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.requestRedirect) + && has(f.requestRedirect.path) && f.requestRedirect.path.type + == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) + ? ((size(self.matches) != 1 || !has(self.matches[0].path) || + self.matches[0].path.type != ''PathPrefix'') ? false : true) + : true' + - message: When using URLRewrite filter with path.replacePrefixMatch, + exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.urlRewrite) + && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' + && has(f.urlRewrite.path.replacePrefixMatch))) ? ((size(self.matches) + != 1 || !has(self.matches[0].path) || self.matches[0].path.type + != ''PathPrefix'') ? false : true) : true' + - message: Within backendRefs, when using RequestRedirect filter + with path.replacePrefixMatch, exactly one PathPrefix match must + be specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, + (has(b.filters) && b.filters.exists_one(f, has(f.requestRedirect) + && has(f.requestRedirect.path) && f.requestRedirect.path.type + == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) + )) ? ((size(self.matches) != 1 || !has(self.matches[0].path) + || self.matches[0].path.type != ''PathPrefix'') ? false : true) + : true' + - message: Within backendRefs, When using URLRewrite filter with + path.replacePrefixMatch, exactly one PathPrefix match must be + specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, + (has(b.filters) && b.filters.exists_one(f, has(f.urlRewrite) + && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' + && has(f.urlRewrite.path.replacePrefixMatch))) )) ? ((size(self.matches) + != 1 || !has(self.matches[0].path) || self.matches[0].path.type + != ''PathPrefix'') ? false : true) : true' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: While 16 rules and 64 matches per rule are allowed, the + total number of matches across all rules in a route must be less + than 128 + rule: '(self.size() > 0 ? self[0].matches.size() : 0) + (self.size() + > 1 ? self[1].matches.size() : 0) + (self.size() > 2 ? self[2].matches.size() + : 0) + (self.size() > 3 ? self[3].matches.size() : 0) + (self.size() + > 4 ? self[4].matches.size() : 0) + (self.size() > 5 ? self[5].matches.size() + : 0) + (self.size() > 6 ? self[6].matches.size() : 0) + (self.size() + > 7 ? self[7].matches.size() : 0) + (self.size() > 8 ? self[8].matches.size() + : 0) + (self.size() > 9 ? self[9].matches.size() : 0) + (self.size() + > 10 ? self[10].matches.size() : 0) + (self.size() > 11 ? self[11].matches.size() + : 0) + (self.size() > 12 ? self[12].matches.size() : 0) + (self.size() + > 13 ? self[13].matches.size() : 0) + (self.size() > 14 ? self[14].matches.size() + : 0) + (self.size() > 15 ? self[15].matches.size() : 0) <= 128' + - message: Rule name must be unique within the route + rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) + && l1.name == l2.name)) + type: object + status: + description: Status defines the current state of HTTPRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: |- + HTTPRoute provides a way to route HTTP requests. This includes the capability + to match requests by hostname, path, header, or query param. Filters can be + used to specify additional processing steps. Backends specify where matching + requests should be routed. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of HTTPRoute. + properties: + hostnames: + description: |- + Hostnames defines a set of hostnames that should match against the HTTP Host + header to select a HTTPRoute used to process the request. Implementations + MUST ignore any port value specified in the HTTP Host header while + performing a match and (absent of any applicable header modification + configuration) MUST forward this header unmodified to the backend. + + Valid values for Hostnames are determined by RFC 1123 definition of a + hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + If a hostname is specified by both the Listener and HTTPRoute, there + must be at least one intersecting hostname for the HTTPRoute to be + attached to the Listener. For example: + + * A Listener with `test.example.com` as the hostname matches HTTPRoutes + that have either not specified any hostnames, or have specified at + least one of `test.example.com` or `*.example.com`. + * A Listener with `*.example.com` as the hostname matches HTTPRoutes + that have either not specified any hostnames or have specified at least + one hostname that matches the Listener hostname. For example, + `*.example.com`, `test.example.com`, and `foo.test.example.com` would + all match. On the other hand, `example.com` and `test.example.net` would + not match. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + + If both the Listener and HTTPRoute have specified hostnames, any + HTTPRoute hostnames that do not match the Listener hostname MUST be + ignored. For example, if a Listener specified `*.example.com`, and the + HTTPRoute specified `test.example.com` and `test.example.net`, + `test.example.net` must not be considered for a match. + + If both the Listener and HTTPRoute have specified hostnames, and none + match with the criteria above, then the HTTPRoute is not accepted. The + implementation must raise an 'Accepted' Condition with a status of + `False` in the corresponding RouteParentStatus. + + In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. + overlapping wildcard matching and exact matching hostnames), precedence must + be given to rules from the HTTPRoute with the largest number of: + + * Characters in a matching non-wildcard hostname. + * Characters in a matching hostname. + + If ties exist across multiple Routes, the matching precedence rules for + HTTPRouteMatches takes over. + + Support: Core + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) + rules: + default: + - matches: + - path: + type: PathPrefix + value: / + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: |- + HTTPRouteRule defines semantics for matching an HTTP request based on + conditions (matches), processing it (filters), and forwarding the request to + an API object (backendRefs). + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. + + Failure behavior here depends on how many BackendRefs are specified and + how many are invalid. + + If *all* entries in BackendRefs are invalid, and there are also no filters + specified in this route rule, *all* traffic which matches this rule MUST + receive a 500 status code. + + See the HTTPBackendRef definition for the rules about what makes a single + HTTPBackendRef invalid. + + When a HTTPBackendRef is invalid, 500 status codes MUST be returned for + requests that would have otherwise been routed to an invalid backend. If + multiple backends are specified, and some are invalid, the proportion of + requests that would otherwise have been routed to an invalid backend + MUST receive a 500 status code. + + For example, if two backends are specified with equal weights, and one is + invalid, 50 percent of traffic must receive a 500. Implementations may + choose how that 50 percent is determined. + + When a HTTPBackendRef refers to a Service that has no ready endpoints, + implementations SHOULD return a 503 for requests to that backend instead. + If an implementation chooses to do this, all of the above rules for 500 responses + MUST also apply for responses that return a 503. + + Support: Core for Kubernetes Service + + Support: Extended for Kubernetes ServiceImport + + Support: Implementation-specific for any other resource + + Support for weight: Core + items: + description: |- + HTTPBackendRef defines how a HTTPRoute forwards a HTTP request. + + Note that when a namespace different than the local namespace is specified, a + ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + + When the BackendRef points to a Kubernetes Service, implementations SHOULD + honor the appProtocol field if it is set for the target Service Port. + + Implementations supporting appProtocol SHOULD recognize the Kubernetes + Standard Application Protocols defined in KEP-3726. + + If a Service appProtocol isn't specified, an implementation MAY infer the + backend protocol through its own means. Implementations MAY infer the + protocol from the Route type referring to the backend Service. + + If a Route is not able to send traffic to the backend using the specified + protocol then the backend is considered invalid. Implementations MUST set the + "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. + properties: + filters: + description: |- + Filters defined at this level should be executed if and only if the + request is being forwarded to the backend defined here. + + Support: Implementation-specific (For broader support of filters, use the + Filters field in HTTPRouteRule.) + items: + description: |- + HTTPRouteFilter defines processing steps that must be completed during the + request or response lifecycle. HTTPRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + cors: + description: |- + CORS defines a schema for a filter that responds to the + cross-origin request based on HTTP response header. + + Support: Extended + properties: + allowCredentials: + description: |- + AllowCredentials indicates whether the actual cross-origin request allows + to include credentials. + + The only valid value for the `Access-Control-Allow-Credentials` response + header is true (case-sensitive). + + If the credentials are not allowed in cross-origin requests, the gateway + will omit the header `Access-Control-Allow-Credentials` entirely rather + than setting its value to false. + + Support: Extended + enum: + - true + type: boolean + allowHeaders: + description: |- + AllowHeaders indicates which HTTP request headers are supported for + accessing the requested resource. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Allow-Headers` + response header are separated by a comma (","). + + When the `AllowHeaders` field is configured with one or more headers, the + gateway must return the `Access-Control-Allow-Headers` response header + which value is present in the `AllowHeaders` field. + + If any header name in the `Access-Control-Request-Headers` request header + is not included in the list of header names specified by the response + header `Access-Control-Allow-Headers`, it will present an error on the + client side. + + If any header name in the `Access-Control-Allow-Headers` response header + does not recognize by the client, it will also occur an error on the + client side. + + A wildcard indicates that the requests with all HTTP headers are allowed. + The `Access-Control-Allow-Headers` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowHeaders` field + specified with the `*` wildcard, the gateway must specify one or more + HTTP headers in the value of the `Access-Control-Allow-Headers` response + header. The value of the header `Access-Control-Allow-Headers` is same as + the `Access-Control-Request-Headers` header provided by the client. If + the header `Access-Control-Request-Headers` is not included in the + request, the gateway will omit the `Access-Control-Allow-Headers` + response header, instead of specifying the `*` wildcard. A Gateway + implementation may choose to add implementation-specific default headers. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + allowMethods: + description: |- + AllowMethods indicates which HTTP methods are supported for accessing the + requested resource. + + Valid values are any method defined by RFC9110, along with the special + value `*`, which represents all HTTP methods are allowed. + + Method names are case sensitive, so these values are also case-sensitive. + (See https://www.rfc-editor.org/rfc/rfc2616#section-5.1.1) + + Multiple method names in the value of the `Access-Control-Allow-Methods` + response header are separated by a comma (","). + + A CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`. + (See https://fetch.spec.whatwg.org/#cors-safelisted-method) The + CORS-safelisted methods are always allowed, regardless of whether they + are specified in the `AllowMethods` field. + + When the `AllowMethods` field is configured with one or more methods, the + gateway must return the `Access-Control-Allow-Methods` response header + which value is present in the `AllowMethods` field. + + If the HTTP method of the `Access-Control-Request-Method` request header + is not included in the list of methods specified by the response header + `Access-Control-Allow-Methods`, it will present an error on the client + side. + + The `Access-Control-Allow-Methods` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowMethods` field + specified with the `*` wildcard, the gateway must specify one HTTP method + in the value of the Access-Control-Allow-Methods response header. The + value of the header `Access-Control-Allow-Methods` is same as the + `Access-Control-Request-Method` header provided by the client. If the + header `Access-Control-Request-Method` is not included in the request, + the gateway will omit the `Access-Control-Allow-Methods` response header, + instead of specifying the `*` wildcard. A Gateway implementation may + choose to add implementation-specific default methods. + + Support: Extended + items: + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + - '*' + type: string + maxItems: 9 + type: array + x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowMethods cannot contain '*' alongside + other methods + rule: '!(''*'' in self && self.size() > 1)' + allowOrigins: + description: |- + AllowOrigins indicates whether the response can be shared with requested + resource from the given `Origin`. + + The `Origin` consists of a scheme and a host, with an optional port, and + takes the form `://(:)`. + + Valid values for scheme are: `http` and `https`. + + Valid values for port are any integer between 1 and 65535 (the list of + available TCP/UDP ports). Note that, if not included, port `80` is + assumed for `http` scheme origins, and port `443` is assumed for `https` + origins. This may affect origin matching. + + The host part of the origin may contain the wildcard character `*`. These + wildcard characters behave as follows: + + * `*` is a greedy match to the _left_, including any number of + DNS labels to the left of its position. This also means that + `*` will include any number of period `.` characters to the + left of its position. + * A wildcard by itself matches all hosts. + + An origin value that includes _only_ the `*` character indicates requests + from all `Origin`s are allowed. + + When the `AllowOrigins` field is configured with multiple origins, it + means the server supports clients from multiple origins. If the request + `Origin` matches the configured allowed origins, the gateway must return + the given `Origin` and sets value of the header + `Access-Control-Allow-Origin` same as the `Origin` header provided by the + client. + + The status code of a successful response to a "preflight" request is + always an OK status (i.e., 204 or 200). + + If the request `Origin` does not match the configured allowed origins, + the gateway returns 204/200 response but doesn't set the relevant + cross-origin response headers. Alternatively, the gateway responds with + 403 status to the "preflight" request is denied, coupled with omitting + the CORS headers. The cross-origin request fails on the client side. + Therefore, the client doesn't attempt the actual cross-origin request. + + The `Access-Control-Allow-Origin` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowOrigins` field + specified with the `*` wildcard, the gateway must return a single origin + in the value of the `Access-Control-Allow-Origin` response header, + instead of specifying the `*` wildcard. The value of the header + `Access-Control-Allow-Origin` is same as the `Origin` header provided by + the client. + + Support: Extended + items: + description: |- + The AbsoluteURI MUST NOT be a relative URI, and it MUST follow the URI syntax and + encoding rules specified in RFC3986. The AbsoluteURI MUST include both a + scheme (e.g., "http" or "spiffe") and a scheme-specific-part. URIs that + include an authority MUST include a fully qualified domain name or + IP address as the host. + maxLength: 253 + minLength: 1 + pattern: ^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))? + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + exposeHeaders: + description: |- + ExposeHeaders indicates which HTTP response headers can be exposed + to client-side scripts in response to a cross-origin request. + + A CORS-safelisted response header is an HTTP header in a CORS response + that it is considered safe to expose to the client scripts. + The CORS-safelisted response headers include the following headers: + `Cache-Control` + `Content-Language` + `Content-Length` + `Content-Type` + `Expires` + `Last-Modified` + `Pragma` + (See https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name) + The CORS-safelisted response headers are exposed to client by default. + + When an HTTP header name is specified using the `ExposeHeaders` field, + this additional header will be exposed as part of the response to the + client. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Expose-Headers` + response header are separated by a comma (","). + + A wildcard indicates that the responses with all HTTP headers are exposed + to clients. The `Access-Control-Expose-Headers` response header can only + use `*` wildcard as value when the `AllowCredentials` field is + unspecified. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + maxAge: + default: 5 + description: |- + MaxAge indicates the duration (in seconds) for the client to cache the + results of a "preflight" request. + + The information provided by the `Access-Control-Allow-Methods` and + `Access-Control-Allow-Headers` response headers can be cached by the + client until the time specified by `Access-Control-Max-Age` elapses. + + The default value of `Access-Control-Max-Age` response header is 5 + (seconds). + format: int32 + minimum: 1 + type: integer + type: object + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + This filter can be used multiple times within the same rule. + + Support: Implementation-specific + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal + to denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be + specified in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + requestRedirect: + description: |- + RequestRedirect defines a schema for a filter that responds to the + request with an HTTP redirection. + + Support: Core + properties: + hostname: + description: |- + Hostname is the hostname to be used in the value of the `Location` + header in the response. + When empty, the hostname in the `Host` header of the request is used. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines parameters used to modify the path of the incoming request. + The modified path is then used to construct the `Location` header. When + empty, the request path is used as-is. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified + when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? + has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when + replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type + == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified + when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' + ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' + when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + port: + description: |- + Port is the port to be used in the value of the `Location` + header in the response. + + If no port is specified, the redirect port MUST be derived using the + following rules: + + * If redirect scheme is not-empty, the redirect port MUST be the well-known + port associated with the redirect scheme. Specifically "http" to port 80 + and "https" to port 443. If the redirect scheme does not have a + well-known port, the listener port of the Gateway SHOULD be used. + * If redirect scheme is empty, the redirect port MUST be the Gateway + Listener port. + + Implementations SHOULD NOT add the port number in the 'Location' + header in the following cases: + + * A Location header that will use HTTP (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 80. + * A Location header that will use HTTPS (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 443. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: |- + Scheme is the scheme to be used in the value of the `Location` header in + the response. When empty, the scheme of the request is used. + + Scheme redirects can affect the port of the redirect, for more information, + refer to the documentation for the port field of this filter. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Extended + enum: + - http + - https + type: string + statusCode: + default: 302 + description: |- + StatusCode is the HTTP status code to be used in response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Core + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations must support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by + specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` should be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + - CORS + type: string + urlRewrite: + description: |- + URLRewrite defines a schema for a filter that modifies a request during forwarding. + + Support: Extended + properties: + hostname: + description: |- + Hostname is the value to be used to replace the Host header value during + forwarding. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines a path rewrite. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified + when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? + has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when + replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type + == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified + when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' + ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' + when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil + if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type + != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type + == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil + if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type + != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for + RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == + ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the + filter.type is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != + ''RequestRedirect'')' + - message: filter.requestRedirect must be specified + for RequestRedirect filter.type + rule: '!(!has(self.requestRedirect) && self.type == + ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type + is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite + filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for + ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + - message: filter.cors must be nil if the filter.type + is not CORS + rule: '!(has(self.cors) && self.type != ''CORS'')' + - message: filter.cors must be specified for CORS filter.type + rule: '!(!has(self.cors) && self.type == ''CORS'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') + && self.exists(f, f.type == ''URLRewrite''))' + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') + && self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() + <= 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() + <= 1 + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + filters: + description: |- + Filters define the filters that are applied to requests that match + this rule. + + Wherever possible, implementations SHOULD implement filters in the order + they are specified. + + Implementations MAY choose to implement this ordering strictly, rejecting + any combination or order of filters that cannot be supported. If implementations + choose a strict interpretation of filter ordering, they MUST clearly document + that behavior. + + To reject an invalid combination or order of filters, implementations SHOULD + consider the Route Rules with this configuration invalid. If all Route Rules + in a Route are invalid, the entire Route would be considered invalid. If only + a portion of Route Rules are invalid, implementations MUST set the + "PartiallyInvalid" condition for the Route. + + Conformance-levels at this level are defined based on the type of filter: + + - ALL core filters MUST be supported by all implementations. + - Implementers are encouraged to support extended filters. + - Implementation-specific custom filters have no API guarantees across + implementations. + + Specifying the same filter multiple times is not supported unless explicitly + indicated in the filter. + + All filters are expected to be compatible with each other except for the + URLRewrite and RequestRedirect filters, which may not be combined. If an + implementation cannot support other combinations of filters, they must clearly + document that limitation. In cases where incompatible or unsupported + filters are specified and cause the `Accepted` condition to be set to status + `False`, implementations may use the `IncompatibleFilters` reason to specify + this configuration error. + + Support: Core + items: + description: |- + HTTPRouteFilter defines processing steps that must be completed during the + request or response lifecycle. HTTPRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + cors: + description: |- + CORS defines a schema for a filter that responds to the + cross-origin request based on HTTP response header. + + Support: Extended + properties: + allowCredentials: + description: |- + AllowCredentials indicates whether the actual cross-origin request allows + to include credentials. + + The only valid value for the `Access-Control-Allow-Credentials` response + header is true (case-sensitive). + + If the credentials are not allowed in cross-origin requests, the gateway + will omit the header `Access-Control-Allow-Credentials` entirely rather + than setting its value to false. + + Support: Extended + enum: + - true + type: boolean + allowHeaders: + description: |- + AllowHeaders indicates which HTTP request headers are supported for + accessing the requested resource. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Allow-Headers` + response header are separated by a comma (","). + + When the `AllowHeaders` field is configured with one or more headers, the + gateway must return the `Access-Control-Allow-Headers` response header + which value is present in the `AllowHeaders` field. + + If any header name in the `Access-Control-Request-Headers` request header + is not included in the list of header names specified by the response + header `Access-Control-Allow-Headers`, it will present an error on the + client side. + + If any header name in the `Access-Control-Allow-Headers` response header + does not recognize by the client, it will also occur an error on the + client side. + + A wildcard indicates that the requests with all HTTP headers are allowed. + The `Access-Control-Allow-Headers` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowHeaders` field + specified with the `*` wildcard, the gateway must specify one or more + HTTP headers in the value of the `Access-Control-Allow-Headers` response + header. The value of the header `Access-Control-Allow-Headers` is same as + the `Access-Control-Request-Headers` header provided by the client. If + the header `Access-Control-Request-Headers` is not included in the + request, the gateway will omit the `Access-Control-Allow-Headers` + response header, instead of specifying the `*` wildcard. A Gateway + implementation may choose to add implementation-specific default headers. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + allowMethods: + description: |- + AllowMethods indicates which HTTP methods are supported for accessing the + requested resource. + + Valid values are any method defined by RFC9110, along with the special + value `*`, which represents all HTTP methods are allowed. + + Method names are case sensitive, so these values are also case-sensitive. + (See https://www.rfc-editor.org/rfc/rfc2616#section-5.1.1) + + Multiple method names in the value of the `Access-Control-Allow-Methods` + response header are separated by a comma (","). + + A CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`. + (See https://fetch.spec.whatwg.org/#cors-safelisted-method) The + CORS-safelisted methods are always allowed, regardless of whether they + are specified in the `AllowMethods` field. + + When the `AllowMethods` field is configured with one or more methods, the + gateway must return the `Access-Control-Allow-Methods` response header + which value is present in the `AllowMethods` field. + + If the HTTP method of the `Access-Control-Request-Method` request header + is not included in the list of methods specified by the response header + `Access-Control-Allow-Methods`, it will present an error on the client + side. + + The `Access-Control-Allow-Methods` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowMethods` field + specified with the `*` wildcard, the gateway must specify one HTTP method + in the value of the Access-Control-Allow-Methods response header. The + value of the header `Access-Control-Allow-Methods` is same as the + `Access-Control-Request-Method` header provided by the client. If the + header `Access-Control-Request-Method` is not included in the request, + the gateway will omit the `Access-Control-Allow-Methods` response header, + instead of specifying the `*` wildcard. A Gateway implementation may + choose to add implementation-specific default methods. + + Support: Extended + items: + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + - '*' + type: string + maxItems: 9 + type: array + x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowMethods cannot contain '*' alongside + other methods + rule: '!(''*'' in self && self.size() > 1)' + allowOrigins: + description: |- + AllowOrigins indicates whether the response can be shared with requested + resource from the given `Origin`. + + The `Origin` consists of a scheme and a host, with an optional port, and + takes the form `://(:)`. + + Valid values for scheme are: `http` and `https`. + + Valid values for port are any integer between 1 and 65535 (the list of + available TCP/UDP ports). Note that, if not included, port `80` is + assumed for `http` scheme origins, and port `443` is assumed for `https` + origins. This may affect origin matching. + + The host part of the origin may contain the wildcard character `*`. These + wildcard characters behave as follows: + + * `*` is a greedy match to the _left_, including any number of + DNS labels to the left of its position. This also means that + `*` will include any number of period `.` characters to the + left of its position. + * A wildcard by itself matches all hosts. + + An origin value that includes _only_ the `*` character indicates requests + from all `Origin`s are allowed. + + When the `AllowOrigins` field is configured with multiple origins, it + means the server supports clients from multiple origins. If the request + `Origin` matches the configured allowed origins, the gateway must return + the given `Origin` and sets value of the header + `Access-Control-Allow-Origin` same as the `Origin` header provided by the + client. + + The status code of a successful response to a "preflight" request is + always an OK status (i.e., 204 or 200). + + If the request `Origin` does not match the configured allowed origins, + the gateway returns 204/200 response but doesn't set the relevant + cross-origin response headers. Alternatively, the gateway responds with + 403 status to the "preflight" request is denied, coupled with omitting + the CORS headers. The cross-origin request fails on the client side. + Therefore, the client doesn't attempt the actual cross-origin request. + + The `Access-Control-Allow-Origin` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowOrigins` field + specified with the `*` wildcard, the gateway must return a single origin + in the value of the `Access-Control-Allow-Origin` response header, + instead of specifying the `*` wildcard. The value of the header + `Access-Control-Allow-Origin` is same as the `Origin` header provided by + the client. + + Support: Extended + items: + description: |- + The AbsoluteURI MUST NOT be a relative URI, and it MUST follow the URI syntax and + encoding rules specified in RFC3986. The AbsoluteURI MUST include both a + scheme (e.g., "http" or "spiffe") and a scheme-specific-part. URIs that + include an authority MUST include a fully qualified domain name or + IP address as the host. + maxLength: 253 + minLength: 1 + pattern: ^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))? + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + exposeHeaders: + description: |- + ExposeHeaders indicates which HTTP response headers can be exposed + to client-side scripts in response to a cross-origin request. + + A CORS-safelisted response header is an HTTP header in a CORS response + that it is considered safe to expose to the client scripts. + The CORS-safelisted response headers include the following headers: + `Cache-Control` + `Content-Language` + `Content-Length` + `Content-Type` + `Expires` + `Last-Modified` + `Pragma` + (See https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name) + The CORS-safelisted response headers are exposed to client by default. + + When an HTTP header name is specified using the `ExposeHeaders` field, + this additional header will be exposed as part of the response to the + client. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Expose-Headers` + response header are separated by a comma (","). + + A wildcard indicates that the responses with all HTTP headers are exposed + to clients. The `Access-Control-Expose-Headers` response header can only + use `*` wildcard as value when the `AllowCredentials` field is + unspecified. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + maxAge: + default: 5 + description: |- + MaxAge indicates the duration (in seconds) for the client to cache the + results of a "preflight" request. + + The information provided by the `Access-Control-Allow-Methods` and + `Access-Control-Allow-Headers` response headers can be cached by the + client until the time specified by `Access-Control-Max-Age` elapses. + + The default value of `Access-Control-Max-Age` response header is 5 + (seconds). + format: int32 + minimum: 1 + type: integer + type: object + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + This filter can be used multiple times within the same rule. + + Support: Implementation-specific + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal to + denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be specified + in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + requestRedirect: + description: |- + RequestRedirect defines a schema for a filter that responds to the + request with an HTTP redirection. + + Support: Core + properties: + hostname: + description: |- + Hostname is the hostname to be used in the value of the `Location` + header in the response. + When empty, the hostname in the `Host` header of the request is used. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines parameters used to modify the path of the incoming request. + The modified path is then used to construct the `Location` header. When + empty, the request path is used as-is. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when + type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) + : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath + is set + rule: 'has(self.replaceFullPath) ? self.type == + ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when + type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) + : true' + - message: type must be 'ReplacePrefixMatch' when + replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + port: + description: |- + Port is the port to be used in the value of the `Location` + header in the response. + + If no port is specified, the redirect port MUST be derived using the + following rules: + + * If redirect scheme is not-empty, the redirect port MUST be the well-known + port associated with the redirect scheme. Specifically "http" to port 80 + and "https" to port 443. If the redirect scheme does not have a + well-known port, the listener port of the Gateway SHOULD be used. + * If redirect scheme is empty, the redirect port MUST be the Gateway + Listener port. + + Implementations SHOULD NOT add the port number in the 'Location' + header in the following cases: + + * A Location header that will use HTTP (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 80. + * A Location header that will use HTTPS (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 443. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: |- + Scheme is the scheme to be used in the value of the `Location` header in + the response. When empty, the scheme of the request is used. + + Scheme redirects can affect the port of the redirect, for more information, + refer to the documentation for the port field of this filter. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Extended + enum: + - http + - https + type: string + statusCode: + default: 302 + description: |- + StatusCode is the HTTP status code to be used in response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Core + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations must support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by + specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` should be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + - CORS + type: string + urlRewrite: + description: |- + URLRewrite defines a schema for a filter that modifies a request during forwarding. + + Support: Extended + properties: + hostname: + description: |- + Hostname is the value to be used to replace the Host header value during + forwarding. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines a path rewrite. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when + type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) + : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath + is set + rule: 'has(self.replaceFullPath) ? self.type == + ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when + type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) + : true' + - message: type must be 'ReplacePrefixMatch' when + replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the + filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != + ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == + ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the + filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != + ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror + filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the filter.type + is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')' + - message: filter.requestRedirect must be specified for RequestRedirect + filter.type + rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type + is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite + filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef + filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + - message: filter.cors must be nil if the filter.type is not + CORS + rule: '!(has(self.cors) && self.type != ''CORS'')' + - message: filter.cors must be specified for CORS filter.type + rule: '!(!has(self.cors) && self.type == ''CORS'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') && + self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() <= + 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() <= 1 + matches: + default: + - path: + type: PathPrefix + value: / + description: |- + Matches define conditions used for matching the rule against incoming + HTTP requests. Each match is independent, i.e. this rule will be matched + if **any** one of the matches is satisfied. + + For example, take the following matches configuration: + + ``` + matches: + - path: + value: "/foo" + headers: + - name: "version" + value: "v2" + - path: + value: "/v2/foo" + ``` + + For a request to match against this rule, a request must satisfy + EITHER of the two conditions: + + - path prefixed with `/foo` AND contains the header `version: v2` + - path prefix of `/v2/foo` + + See the documentation for HTTPRouteMatch on how to specify multiple + match conditions that should be ANDed together. + + If no matches are specified, the default is a prefix + path match on "/", which has the effect of matching every + HTTP request. + + Proxy or Load Balancer routing configuration generated from HTTPRoutes + MUST prioritize matches based on the following criteria, continuing on + ties. Across all rules specified on applicable Routes, precedence must be + given to the match having: + + * "Exact" path match. + * "Prefix" path match with largest number of characters. + * Method match. + * Largest number of header matches. + * Largest number of query param matches. + + Note: The precedence of RegularExpression path matches are implementation-specific. + + If ties still exist across multiple Routes, matching precedence MUST be + determined in order of the following criteria, continuing on ties: + + * The oldest Route based on creation timestamp. + * The Route appearing first in alphabetical order by + "{namespace}/{name}". + + If ties still exist within an HTTPRoute, matching precedence MUST be granted + to the FIRST matching rule (in list order) with a match meeting the above + criteria. + + When no rules matching a request have been successfully attached to the + parent a request is coming from, a HTTP 404 status code MUST be returned. + items: + description: "HTTPRouteMatch defines the predicate used to + match requests to a given\naction. Multiple match types + are ANDed together, i.e. the match will\nevaluate to true + only if all conditions are satisfied.\n\nFor example, the + match below will match a HTTP request only if its path\nstarts + with `/foo` AND it contains the `version: v1` header:\n\n```\nmatch:\n\n\tpath:\n\t + \ value: \"/foo\"\n\theaders:\n\t- name: \"version\"\n\t + \ value \"v1\"\n\n```" + properties: + headers: + description: |- + Headers specifies HTTP request header matchers. Multiple match values are + ANDed together, meaning, a request must match all the specified headers + to select the route. + items: + description: |- + HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request + headers. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + + When a header is repeated in an HTTP request, it is + implementation-specific behavior as to how this is represented. + Generally, proxies should follow the guidance from the RFC: + https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding + processing a repeated header, with special handling for "Set-Cookie". + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the header. + + Support: Core (Exact) + + Support: Implementation-specific (RegularExpression) + + Since RegularExpression HeaderMatchType has implementation-specific + conformance, implementations can support POSIX, PCRE or any other dialects + of regular expressions. Please read the implementation's documentation to + determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP Header to + be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: |- + Method specifies HTTP method matcher. + When specified, this route will be matched only if the request has the + specified method. + + Support: Extended + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: |- + Path specifies a HTTP request path matcher. If this field is not + specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: |- + Type specifies how to match against the path Value. + + Support: Core (Exact, PathPrefix) + + Support: Implementation-specific (RegularExpression) + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path to match against. + maxLength: 1024 + type: string + type: object + x-kubernetes-validations: + - message: value must be an absolute path and start with + '/' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.startsWith(''/'') + : true' + - message: must not contain '//' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''//'') + : true' + - message: must not contain '/./' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/./'') + : true' + - message: must not contain '/../' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/../'') + : true' + - message: must not contain '%2f' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2f'') + : true' + - message: must not contain '%2F' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2F'') + : true' + - message: must not contain '#' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''#'') + : true' + - message: must not end with '/..' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/..'') + : true' + - message: must not end with '/.' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/.'') + : true' + - message: type must be one of ['Exact', 'PathPrefix', + 'RegularExpression'] + rule: self.type in ['Exact','PathPrefix'] || self.type + == 'RegularExpression' + - message: must only contain valid characters (matching + ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$) + for types ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.matches(r"""^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$""") + : true' + queryParams: + description: |- + QueryParams specifies HTTP query parameter matchers. Multiple match + values are ANDed together, meaning, a request must match all the + specified query parameters to select the route. + + Support: Extended + items: + description: |- + HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP + query parameters. + properties: + name: + description: |- + Name is the name of the HTTP query param to be matched. This must be an + exact string match. (See + https://tools.ietf.org/html/rfc7230#section-2.7.3). + + If multiple entries specify equivalent query param names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent query param name MUST be ignored. + + If a query param is repeated in an HTTP request, the behavior is + purposely left undefined, since different data planes have different + capabilities. However, it is *recommended* that implementations should + match against the first value of the param if the data plane supports it, + as this behavior is expected in other load balancing contexts outside of + the Gateway API. + + Users SHOULD NOT route traffic based on repeated query params to guard + themselves against potential differences in the implementations. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the query parameter. + + Support: Extended (Exact) + + Support: Implementation-specific (RegularExpression) + + Since RegularExpression QueryParamMatchType has Implementation-specific + conformance, implementations can support POSIX, PCRE or any other + dialects of regular expressions. Please read the implementation's + documentation to determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP query param + to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 64 + type: array + name: + description: |- + Name is the name of the route rule. This name MUST be unique within a Route if it is set. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + retry: + description: |- + Retry defines the configuration for when to retry an HTTP request. + + Support: Extended + properties: + attempts: + description: |- + Attempts specifies the maximum number of times an individual request + from the gateway to a backend should be retried. + + If the maximum number of retries has been attempted without a successful + response from the backend, the Gateway MUST return an error. + + When this field is unspecified, the number of times to attempt to retry + a backend request is implementation-specific. + + Support: Extended + type: integer + backoff: + description: |- + Backoff specifies the minimum duration a Gateway should wait between + retry attempts and is represented in Gateway API Duration formatting. + + For example, setting the `rules[].retry.backoff` field to the value + `100ms` will cause a backend request to first be retried approximately + 100 milliseconds after timing out or receiving a response code configured + to be retryable. + + An implementation MAY use an exponential or alternative backoff strategy + for subsequent retry attempts, MAY cap the maximum backoff duration to + some amount greater than the specified minimum, and MAY add arbitrary + jitter to stagger requests, as long as unsuccessful backend requests are + not retried before the configured minimum duration. + + If a Request timeout (`rules[].timeouts.request`) is configured on the + route, the entire duration of the initial request and any retry attempts + MUST not exceed the Request timeout duration. If any retry attempts are + still in progress when the Request timeout duration has been reached, + these SHOULD be canceled if possible and the Gateway MUST immediately + return a timeout error. + + If a BackendRequest timeout (`rules[].timeouts.backendRequest`) is + configured on the route, any retry attempts which reach the configured + BackendRequest timeout duration without a response SHOULD be canceled if + possible and the Gateway should wait for at least the specified backoff + duration before attempting to retry the backend request again. + + If a BackendRequest timeout is _not_ configured on the route, retry + attempts MAY time out after an implementation default duration, or MAY + remain pending until a configured Request timeout or implementation + default duration for total request time is reached. + + When this field is unspecified, the time to wait between retry attempts + is implementation-specific. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + codes: + description: |- + Codes defines the HTTP response status codes for which a backend request + should be retried. + + Support: Extended + items: + description: |- + HTTPRouteRetryStatusCode defines an HTTP response status code for + which a backend request should be retried. + + Implementations MUST support the following status codes as retryable: + + * 500 + * 502 + * 503 + * 504 + + Implementations MAY support specifying additional discrete values in the + 500-599 range. + + Implementations MAY support specifying discrete values in the 400-499 range, + which are often inadvisable to retry. + maximum: 599 + minimum: 400 + type: integer + type: array + type: object + sessionPersistence: + description: |- + SessionPersistence defines and configures session persistence + for the route rule. + + Support: Extended + properties: + absoluteTimeout: + description: |- + AbsoluteTimeout defines the absolute timeout of the persistent + session. Once the AbsoluteTimeout duration has elapsed, the + session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + cookieConfig: + description: |- + CookieConfig provides configuration settings that are specific + to cookie-based session persistence. + + Support: Core + properties: + lifetimeType: + default: Session + description: |- + LifetimeType specifies whether the cookie has a permanent or + session-based lifetime. A permanent cookie persists until its + specified expiry time, defined by the Expires or Max-Age cookie + attributes, while a session cookie is deleted when the current + session ends. + + When set to "Permanent", AbsoluteTimeout indicates the + cookie's lifetime via the Expires or Max-Age cookie attributes + and is required. + + When set to "Session", AbsoluteTimeout indicates the + absolute lifetime of the cookie tracked by the gateway and + is optional. + + Defaults to "Session". + + Support: Core for "Session" type + + Support: Extended for "Permanent" type + enum: + - Permanent + - Session + type: string + type: object + idleTimeout: + description: |- + IdleTimeout defines the idle timeout of the persistent session. + Once the session has been idle for more than the specified + IdleTimeout duration, the session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + sessionName: + description: |- + SessionName defines the name of the persistent session token + which may be reflected in the cookie or the header. Users + should avoid reusing session names to prevent unintended + consequences, such as rejection or unpredictable behavior. + + Support: Implementation-specific + maxLength: 128 + type: string + type: + default: Cookie + description: |- + Type defines the type of session persistence such as through + the use a header or cookie. Defaults to cookie based session + persistence. + + Support: Core for "Cookie" type + + Support: Extended for "Header" type + enum: + - Cookie + - Header + type: string + type: object + x-kubernetes-validations: + - message: AbsoluteTimeout must be specified when cookie lifetimeType + is Permanent + rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType) + || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)' + timeouts: + description: |- + Timeouts defines the timeouts that can be configured for an HTTP request. + + Support: Extended + properties: + backendRequest: + description: |- + BackendRequest specifies a timeout for an individual request from the gateway + to a backend. This covers the time from when the request first starts being + sent from the gateway to when the full response has been received from the backend. + + Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout + completely. Implementations that cannot completely disable the timeout MUST + instead interpret the zero duration as the longest possible value to which + the timeout can be set. + + An entire client HTTP transaction with a gateway, covered by the Request timeout, + may result in more than one call from the gateway to the destination backend, + for example, if automatic retries are supported. + + The value of BackendRequest must be a Gateway API Duration string as defined by + GEP-2257. When this field is unspecified, its behavior is implementation-specific; + when specified, the value of BackendRequest must be no more than the value of the + Request timeout (since the Request timeout encompasses the BackendRequest timeout). + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + request: + description: |- + Request specifies the maximum duration for a gateway to respond to an HTTP request. + If the gateway has not been able to respond before this deadline is met, the gateway + MUST return a timeout error. + + For example, setting the `rules.timeouts.request` field to the value `10s` in an + `HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds + to complete. + + Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout + completely. Implementations that cannot completely disable the timeout MUST + instead interpret the zero duration as the longest possible value to which + the timeout can be set. + + This timeout is intended to cover as close to the whole request-response transaction + as possible although an implementation MAY choose to start the timeout after the entire + request stream has been received instead of immediately after the transaction is + initiated by the client. + + The value of Request is a Gateway API Duration string as defined by GEP-2257. When this + field is unspecified, request timeout behavior is implementation-specific. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + x-kubernetes-validations: + - message: backendRequest timeout cannot be longer than request + timeout + rule: '!(has(self.request) && has(self.backendRequest) && + duration(self.request) != duration(''0s'') && duration(self.backendRequest) + > duration(self.request))' + type: object + x-kubernetes-validations: + - message: RequestRedirect filter must not be used together with + backendRefs + rule: '(has(self.backendRefs) && size(self.backendRefs) > 0) ? + (!has(self.filters) || self.filters.all(f, !has(f.requestRedirect))): + true' + - message: When using RequestRedirect filter with path.replacePrefixMatch, + exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.requestRedirect) + && has(f.requestRedirect.path) && f.requestRedirect.path.type + == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) + ? ((size(self.matches) != 1 || !has(self.matches[0].path) || + self.matches[0].path.type != ''PathPrefix'') ? false : true) + : true' + - message: When using URLRewrite filter with path.replacePrefixMatch, + exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.urlRewrite) + && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' + && has(f.urlRewrite.path.replacePrefixMatch))) ? ((size(self.matches) + != 1 || !has(self.matches[0].path) || self.matches[0].path.type + != ''PathPrefix'') ? false : true) : true' + - message: Within backendRefs, when using RequestRedirect filter + with path.replacePrefixMatch, exactly one PathPrefix match must + be specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, + (has(b.filters) && b.filters.exists_one(f, has(f.requestRedirect) + && has(f.requestRedirect.path) && f.requestRedirect.path.type + == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) + )) ? ((size(self.matches) != 1 || !has(self.matches[0].path) + || self.matches[0].path.type != ''PathPrefix'') ? false : true) + : true' + - message: Within backendRefs, When using URLRewrite filter with + path.replacePrefixMatch, exactly one PathPrefix match must be + specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, + (has(b.filters) && b.filters.exists_one(f, has(f.urlRewrite) + && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' + && has(f.urlRewrite.path.replacePrefixMatch))) )) ? ((size(self.matches) + != 1 || !has(self.matches[0].path) || self.matches[0].path.type + != ''PathPrefix'') ? false : true) : true' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: While 16 rules and 64 matches per rule are allowed, the + total number of matches across all rules in a route must be less + than 128 + rule: '(self.size() > 0 ? self[0].matches.size() : 0) + (self.size() + > 1 ? self[1].matches.size() : 0) + (self.size() > 2 ? self[2].matches.size() + : 0) + (self.size() > 3 ? self[3].matches.size() : 0) + (self.size() + > 4 ? self[4].matches.size() : 0) + (self.size() > 5 ? self[5].matches.size() + : 0) + (self.size() > 6 ? self[6].matches.size() : 0) + (self.size() + > 7 ? self[7].matches.size() : 0) + (self.size() > 8 ? self[8].matches.size() + : 0) + (self.size() > 9 ? self[9].matches.size() : 0) + (self.size() + > 10 ? self[10].matches.size() : 0) + (self.size() > 11 ? self[11].matches.size() + : 0) + (self.size() > 12 ? self[12].matches.size() : 0) + (self.size() + > 13 ? self[13].matches.size() : 0) + (self.size() > 14 ? self[14].matches.size() + : 0) + (self.size() > 15 ? self[15].matches.size() : 0) <= 128' + - message: Rule name must be unique within the route + rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) + && l1.name == l2.name)) + type: object + status: + description: Status defines the current state of HTTPRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_referencegrants.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: referencegrants.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: ReferenceGrant + listKind: ReferenceGrantList + plural: referencegrants + shortNames: + - refgrant + singular: referencegrant + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: |- + ReferenceGrant identifies kinds of resources in other namespaces that are + trusted to reference the specified kinds of resources in the same namespace + as the policy. + + Each ReferenceGrant can be used to represent a unique trust relationship. + Additional Reference Grants can be used to add to the set of trusted + sources of inbound references for the namespace they are defined within. + + All cross-namespace references in Gateway API (with the exception of cross-namespace + Gateway-route attachment) require a ReferenceGrant. + + ReferenceGrant is a form of runtime verification allowing users to assert + which cross-namespace object references are permitted. Implementations that + support ReferenceGrant MUST NOT permit cross-namespace references which have + no grant, and MUST respond to the removal of a grant by revoking the access + that the grant allowed. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of ReferenceGrant. + properties: + from: + description: |- + From describes the trusted namespaces and kinds that can reference the + resources described in "To". Each entry in this list MUST be considered + to be an additional place that references can be valid from, or to put + this another way, entries MUST be combined using OR. + + Support: Core + items: + description: ReferenceGrantFrom describes trusted namespaces and + kinds. + properties: + group: + description: |- + Group is the group of the referent. + When empty, the Kubernetes core API group is inferred. + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: |- + Kind is the kind of the referent. Although implementations may support + additional resources, the following types are part of the "Core" + support level for this field. + + When used to permit a SecretObjectReference: + + * Gateway + + When used to permit a BackendObjectReference: + + * GRPCRoute + * HTTPRoute + * TCPRoute + * TLSRoute + * UDPRoute + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + namespace: + description: |- + Namespace is the namespace of the referent. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - namespace + type: object + maxItems: 16 + minItems: 1 + type: array + to: + description: |- + To describes the resources that may be referenced by the resources + described in "From". Each entry in this list MUST be considered to be an + additional place that references can be valid to, or to put this another + way, entries MUST be combined using OR. + + Support: Core + items: + description: |- + ReferenceGrantTo describes what Kinds are allowed as targets of the + references. + properties: + group: + description: |- + Group is the group of the referent. + When empty, the Kubernetes core API group is inferred. + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: |- + Kind is the kind of the referent. Although implementations may support + additional resources, the following types are part of the "Core" + support level for this field: + + * Secret when used to permit a SecretObjectReference + * Service when used to permit a BackendObjectReference + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. When unspecified, this policy + refers to all resources of the specified Group and Kind in the local + namespace. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - from + - to + type: object + type: object + served: true + storage: true + subresources: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_tcproutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: tcproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: TCPRoute + listKind: TCPRouteList + plural: tcproutes + singular: tcproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: |- + TCPRoute provides a way to route TCP requests. When combined with a Gateway + listener, it can be used to forward connections on the port specified by the + listener to a set of backends specified by the TCPRoute. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of TCPRoute. + properties: + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) + rules: + description: Rules are a list of TCP matchers and actions. + items: + description: TCPRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. If unspecified or invalid (refers to a nonexistent resource or a + Service with no endpoints), the underlying implementation MUST actively + reject connection attempts to this backend. Connection rejections must + respect weight; if an invalid backend is requested to have 80% of + connections, then 80% of connections must be rejected instead. + + Support: Core for Kubernetes Service + + Support: Extended for Kubernetes ServiceImport + + Support: Implementation-specific for any other resource + + Support for weight: Extended + items: + description: |- + BackendRef defines how a Route should forward a request to a Kubernetes + resource. + + Note that when a namespace different than the local namespace is specified, a + ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + + When the BackendRef points to a Kubernetes Service, implementations SHOULD + honor the appProtocol field if it is set for the target Service Port. + + Implementations supporting appProtocol SHOULD recognize the Kubernetes + Standard Application Protocols defined in KEP-3726. + + If a Service appProtocol isn't specified, an implementation MAY infer the + backend protocol through its own means. Implementations MAY infer the + protocol from the Route type referring to the backend Service. + + If a Route is not able to send traffic to the backend using the specified + protocol then the backend is considered invalid. Implementations MUST set the + "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. + + + Note that when the BackendTLSPolicy object is enabled by the implementation, + there are some extra rules about validity to consider here. See the fields + where this struct is used for more information about the exact behavior. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + minItems: 1 + type: array + name: + description: |- + Name is the name of the route rule. This name MUST be unique within a Route if it is set. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-validations: + - message: Rule name must be unique within the route + rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) + && l1.name == l2.name)) + required: + - rules + type: object + status: + description: Status defines the current state of TCPRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_tlsroutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: tlsroutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: TLSRoute + listKind: TLSRouteList + plural: tlsroutes + singular: tlsroute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: |- + The TLSRoute resource is similar to TCPRoute, but can be configured + to match against TLS-specific metadata. This allows more flexibility + in matching streams for a given TLS listener. + + If you need to forward traffic to a single target for a TLS listener, you + could choose to use a TCPRoute with a TLS listener. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of TLSRoute. + properties: + hostnames: + description: |- + Hostnames defines a set of SNI names that should match against the + SNI attribute of TLS ClientHello message in TLS handshake. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed in SNI names per RFC 6066. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + If a hostname is specified by both the Listener and TLSRoute, there + must be at least one intersecting hostname for the TLSRoute to be + attached to the Listener. For example: + + * A Listener with `test.example.com` as the hostname matches TLSRoutes + that have either not specified any hostnames, or have specified at + least one of `test.example.com` or `*.example.com`. + * A Listener with `*.example.com` as the hostname matches TLSRoutes + that have either not specified any hostnames or have specified at least + one hostname that matches the Listener hostname. For example, + `test.example.com` and `*.example.com` would both match. On the other + hand, `example.com` and `test.example.net` would not match. + + If both the Listener and TLSRoute have specified hostnames, any + TLSRoute hostnames that do not match the Listener hostname MUST be + ignored. For example, if a Listener specified `*.example.com`, and the + TLSRoute specified `test.example.com` and `test.example.net`, + `test.example.net` must not be considered for a match. + + If both the Listener and TLSRoute have specified hostnames, and none + match with the criteria above, then the TLSRoute is not accepted. The + implementation must raise an 'Accepted' Condition with a status of + `False` in the corresponding RouteParentStatus. + + Support: Core + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) + rules: + description: Rules are a list of TLS matchers and actions. + items: + description: TLSRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. If unspecified or invalid (refers to a nonexistent resource or + a Service with no endpoints), the rule performs no forwarding; if no + filters are specified that would result in a response being sent, the + underlying implementation must actively reject request attempts to this + backend, by rejecting the connection or returning a 500 status code. + Request rejections must respect weight; if an invalid backend is + requested to have 80% of requests, then 80% of requests must be rejected + instead. + + Support: Core for Kubernetes Service + + Support: Extended for Kubernetes ServiceImport + + Support: Implementation-specific for any other resource + + Support for weight: Extended + items: + description: |- + BackendRef defines how a Route should forward a request to a Kubernetes + resource. + + Note that when a namespace different than the local namespace is specified, a + ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + + When the BackendRef points to a Kubernetes Service, implementations SHOULD + honor the appProtocol field if it is set for the target Service Port. + + Implementations supporting appProtocol SHOULD recognize the Kubernetes + Standard Application Protocols defined in KEP-3726. + + If a Service appProtocol isn't specified, an implementation MAY infer the + backend protocol through its own means. Implementations MAY infer the + protocol from the Route type referring to the backend Service. + + If a Route is not able to send traffic to the backend using the specified + protocol then the backend is considered invalid. Implementations MUST set the + "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. + + + Note that when the BackendTLSPolicy object is enabled by the implementation, + there are some extra rules about validity to consider here. See the fields + where this struct is used for more information about the exact behavior. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + minItems: 1 + type: array + name: + description: |- + Name is the name of the route rule. This name MUST be unique within a Route if it is set. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-validations: + - message: Rule name must be unique within the route + rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) + && l1.name == l2.name)) + required: + - rules + type: object + status: + description: Status defines the current state of TLSRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_udproutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: udproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: UDPRoute + listKind: UDPRouteList + plural: udproutes + singular: udproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: |- + UDPRoute provides a way to route UDP traffic. When combined with a Gateway + listener, it can be used to forward traffic on the port specified by the + listener to a set of backends specified by the UDPRoute. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of UDPRoute. + properties: + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) + rules: + description: Rules are a list of UDP matchers and actions. + items: + description: UDPRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. If unspecified or invalid (refers to a nonexistent resource or a + Service with no endpoints), the underlying implementation MUST actively + reject connection attempts to this backend. Packet drops must + respect weight; if an invalid backend is requested to have 80% of + the packets, then 80% of packets must be dropped instead. + + Support: Core for Kubernetes Service + + Support: Extended for Kubernetes ServiceImport + + Support: Implementation-specific for any other resource + + Support for weight: Extended + items: + description: |- + BackendRef defines how a Route should forward a request to a Kubernetes + resource. + + Note that when a namespace different than the local namespace is specified, a + ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + + When the BackendRef points to a Kubernetes Service, implementations SHOULD + honor the appProtocol field if it is set for the target Service Port. + + Implementations supporting appProtocol SHOULD recognize the Kubernetes + Standard Application Protocols defined in KEP-3726. + + If a Service appProtocol isn't specified, an implementation MAY infer the + backend protocol through its own means. Implementations MAY infer the + protocol from the Route type referring to the backend Service. + + If a Route is not able to send traffic to the backend using the specified + protocol then the backend is considered invalid. Implementations MUST set the + "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. + + + Note that when the BackendTLSPolicy object is enabled by the implementation, + there are some extra rules about validity to consider here. See the fields + where this struct is used for more information about the exact behavior. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + minItems: 1 + type: array + name: + description: |- + Name is the name of the route rule. This name MUST be unique within a Route if it is set. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-validations: + - message: Rule name must be unique within the route + rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) + && l1.name == l2.name)) + required: + - rules + type: object + status: + description: Status defines the current state of UDPRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.x-k8s.io_xbackendtrafficpolicies.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + labels: + gateway.networking.k8s.io/policy: Direct + name: xbackendtrafficpolicies.gateway.networking.x-k8s.io +spec: + group: gateway.networking.x-k8s.io + names: + categories: + - gateway-api + kind: XBackendTrafficPolicy + listKind: XBackendTrafficPolicyList + plural: xbackendtrafficpolicies + shortNames: + - xbtrafficpolicy + singular: xbackendtrafficpolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + XBackendTrafficPolicy defines the configuration for how traffic to a + target backend should be handled. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of BackendTrafficPolicy. + properties: + retryConstraint: + description: |- + RetryConstraint defines the configuration for when to allow or prevent + further retries to a target backend, by dynamically calculating a 'retry + budget'. This budget is calculated based on the percentage of incoming + traffic composed of retries over a given time interval. Once the budget + is exceeded, additional retries will be rejected. + + For example, if the retry budget interval is 10 seconds, there have been + 1000 active requests in the past 10 seconds, and the allowed percentage + of requests that can be retried is 20% (the default), then 200 of those + requests may be composed of retries. Active requests will only be + considered for the duration of the interval when calculating the retry + budget. Retrying the same original request multiple times within the + retry budget interval will lead to each retry being counted towards + calculating the budget. + + Configuring a RetryConstraint in BackendTrafficPolicy is compatible with + HTTPRoute Retry settings for each HTTPRouteRule that targets the same + backend. While the HTTPRouteRule Retry stanza can specify whether a + request will be retried, and the number of retry attempts each client + may perform, RetryConstraint helps prevent cascading failures such as + retry storms during periods of consistent failures. + + After the retry budget has been exceeded, additional retries to the + backend MUST return a 503 response to the client. + + Additional configurations for defining a constraint on retries MAY be + defined in the future. + + Support: Extended + properties: + budget: + default: + interval: 10s + percent: 20 + description: Budget holds the details of the retry budget configuration. + properties: + interval: + default: 10s + description: |- + Interval defines the duration in which requests will be considered + for calculating the budget for retries. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + x-kubernetes-validations: + - message: interval can not be greater than one hour or less + than one second + rule: '!(duration(self) < duration(''1s'') || duration(self) + > duration(''1h''))' + percent: + default: 20 + description: |- + Percent defines the maximum percentage of active requests that may + be made up of retries. + + Support: Extended + maximum: 100 + minimum: 0 + type: integer + type: object + minRetryRate: + default: + count: 10 + interval: 1s + description: |- + MinRetryRate defines the minimum rate of retries that will be allowable + over a specified duration of time. + + The effective overall minimum rate of retries targeting the backend + service may be much higher, as there can be any number of clients which + are applying this setting locally. + + This ensures that requests can still be retried during periods of low + traffic, where the budget for retries may be calculated as a very low + value. + + Support: Extended + properties: + count: + description: |- + Count specifies the number of requests per time interval. + + Support: Extended + maximum: 1000000 + minimum: 1 + type: integer + interval: + description: |- + Interval specifies the divisor of the rate of requests, the amount of + time during which the given count of requests occur. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + x-kubernetes-validations: + - message: interval can not be greater than one hour + rule: '!(duration(self) == duration(''0s'') || duration(self) + > duration(''1h''))' + type: object + type: object + sessionPersistence: + description: |- + SessionPersistence defines and configures session persistence + for the backend. + + Support: Extended + properties: + absoluteTimeout: + description: |- + AbsoluteTimeout defines the absolute timeout of the persistent + session. Once the AbsoluteTimeout duration has elapsed, the + session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + cookieConfig: + description: |- + CookieConfig provides configuration settings that are specific + to cookie-based session persistence. + + Support: Core + properties: + lifetimeType: + default: Session + description: |- + LifetimeType specifies whether the cookie has a permanent or + session-based lifetime. A permanent cookie persists until its + specified expiry time, defined by the Expires or Max-Age cookie + attributes, while a session cookie is deleted when the current + session ends. + + When set to "Permanent", AbsoluteTimeout indicates the + cookie's lifetime via the Expires or Max-Age cookie attributes + and is required. + + When set to "Session", AbsoluteTimeout indicates the + absolute lifetime of the cookie tracked by the gateway and + is optional. + + Defaults to "Session". + + Support: Core for "Session" type + + Support: Extended for "Permanent" type + enum: + - Permanent + - Session + type: string + type: object + idleTimeout: + description: |- + IdleTimeout defines the idle timeout of the persistent session. + Once the session has been idle for more than the specified + IdleTimeout duration, the session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + sessionName: + description: |- + SessionName defines the name of the persistent session token + which may be reflected in the cookie or the header. Users + should avoid reusing session names to prevent unintended + consequences, such as rejection or unpredictable behavior. + + Support: Implementation-specific + maxLength: 128 + type: string + type: + default: Cookie + description: |- + Type defines the type of session persistence such as through + the use a header or cookie. Defaults to cookie based session + persistence. + + Support: Core for "Cookie" type + + Support: Extended for "Header" type + enum: + - Cookie + - Header + type: string + type: object + x-kubernetes-validations: + - message: AbsoluteTimeout must be specified when cookie lifetimeType + is Permanent + rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType) + || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)' + targetRefs: + description: |- + TargetRefs identifies API object(s) to apply this policy to. + Currently, Backends (A grouping of like endpoints such as Service, + ServiceImport, or any implementation-specific backendRef) are the only + valid API target references. + + Currently, a TargetRef can not be scoped to a specific port on a + Service. + items: + description: |- + LocalPolicyTargetReference identifies an API object to apply a direct or + inherited policy to. This should be used as part of Policy resources + that can target Gateway API resources. For more information on how this + policy attachment model works, and a sample Policy resource, refer to + the policy attachment documentation for Gateway API. + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - group + - kind + - name + x-kubernetes-list-type: map + required: + - targetRefs + type: object + status: + description: Status defines the current state of BackendTrafficPolicy. + properties: + ancestors: + description: |- + Ancestors is a list of ancestor resources (usually Gateways) that are + associated with the policy, and the status of the policy with respect to + each ancestor. When this policy attaches to a parent, the controller that + manages the parent and the ancestors MUST add an entry to this list when + the controller first sees the policy and SHOULD update the entry as + appropriate when the relevant ancestor is modified. + + Note that choosing the relevant ancestor is left to the Policy designers; + an important part of Policy design is designing the right object level at + which to namespace this status. + + Note also that implementations MUST ONLY populate ancestor status for + the Ancestor resources they are responsible for. Implementations MUST + use the ControllerName field to uniquely identify the entries in this list + that they are responsible for. + + Note that to achieve this, the list of PolicyAncestorStatus structs + MUST be treated as a map with a composite key, made up of the AncestorRef + and ControllerName fields combined. + + A maximum of 16 ancestors will be represented in this list. An empty list + means the Policy is not relevant for any ancestors. + + If this slice is full, implementations MUST NOT add further entries. + Instead they MUST consider the policy unimplementable and signal that + on any related resources such as the ancestor that would be referenced + here. For example, if this list was full on BackendTLSPolicy, no + additional Gateways would be able to reference the Service targeted by + the BackendTLSPolicy. + items: + description: |- + PolicyAncestorStatus describes the status of a route with respect to an + associated Ancestor. + + Ancestors refer to objects that are either the Target of a policy or above it + in terms of object hierarchy. For example, if a policy targets a Service, the + Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and + the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most + useful object to place Policy status on, so we recommend that implementations + SHOULD use Gateway as the PolicyAncestorStatus object unless the designers + have a _very_ good reason otherwise. + + In the context of policy attachment, the Ancestor is used to distinguish which + resource results in a distinct application of this policy. For example, if a policy + targets a Service, it may have a distinct result per attached Gateway. + + Policies targeting the same resource may have different effects depending on the + ancestors of those resources. For example, different Gateways targeting the same + Service may have different capabilities, especially if they have different underlying + implementations. + + For example, in BackendTLSPolicy, the Policy attaches to a Service that is + used as a backend in a HTTPRoute that is itself attached to a Gateway. + In this case, the relevant object for status is the Gateway, and that is the + ancestor object referred to in this status. + + Note that a parent is also an ancestor, so for objects where the parent is the + relevant object for status, this struct SHOULD still be used. + + This struct is intended to be used in a slice that's effectively a map, + with a composite key made up of the AncestorRef and the ControllerName. + properties: + ancestorRef: + description: |- + AncestorRef corresponds with a ParentRef in the spec that this + PolicyAncestorStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: Conditions describes the status of the Policy with + respect to the given Ancestor. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - controllerName + type: object + maxItems: 16 + type: array + required: + - ancestors + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.x-k8s.io_xlistenersets.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: xlistenersets.gateway.networking.x-k8s.io +spec: + group: gateway.networking.x-k8s.io + names: + categories: + - gateway-api + kind: XListenerSet + listKind: XListenerSetList + plural: xlistenersets + shortNames: + - lset + singular: xlistenerset + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + XListenerSet defines a set of additional listeners + to attach to an existing Gateway. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of ListenerSet. + properties: + listeners: + description: |- + Listeners associated with this ListenerSet. Listeners define + logical endpoints that are bound on this referenced parent Gateway's addresses. + + Listeners in a `Gateway` and their attached `ListenerSets` are concatenated + as a list when programming the underlying infrastructure. Each listener + name does not need to be unique across the Gateway and ListenerSets. + See ListenerEntry.Name for more details. + + Implementations MUST treat the parent Gateway as having the merged + list of all listeners from itself and attached ListenerSets using + the following precedence: + + 1. "parent" Gateway + 2. ListenerSet ordered by creation time (oldest first) + 3. ListenerSet ordered alphabetically by “{namespace}/{name}”. + + An implementation MAY reject listeners by setting the ListenerEntryStatus + `Accepted`` condition to False with the Reason `TooManyListeners` + + If a listener has a conflict, this will be reported in the + Status.ListenerEntryStatus setting the `Conflicted` condition to True. + + Implementations SHOULD be cautious about what information from the + parent or siblings are reported to avoid accidentally leaking + sensitive information that the child would not otherwise have access + to. This can include contents of secrets etc. + items: + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: |- + AllowedRoutes defines the types of routes that MAY be attached to a + Listener and the trusted namespaces where those Route resources MAY be + present. + + Although a client request may match multiple route rules, only one rule + may ultimately receive the request. Matching precedence MUST be + determined in order of the following criteria: + + * The most specific match as defined by the Route type. + * The oldest Route based on creation timestamp. For example, a Route with + a creation timestamp of "2020-09-08 01:02:03" is given precedence over + a Route with a creation timestamp of "2020-09-08 01:02:04". + * If everything else is equivalent, the Route appearing first in + alphabetical order (namespace/name) should be given precedence. For + example, foo/bar is given precedence over foo/baz. + + All valid rules within a Route attached to this Listener should be + implemented. Invalid Route rules can be ignored (sometimes that will mean + the full Route). If a Route rule transitions from valid to invalid, + support for that Route rule should be dropped to ensure consistency. For + example, even if a filter specified by a Route rule is invalid, the rest + of the rules within that Route should still be supported. + properties: + kinds: + description: |- + Kinds specifies the groups and kinds of Routes that are allowed to bind + to this Gateway Listener. When unspecified or empty, the kinds of Routes + selected are determined using the Listener protocol. + + A RouteGroupKind MUST correspond to kinds of Routes that are compatible + with the application protocol specified in the Listener's Protocol field. + If an implementation does not support or recognize this resource type, it + MUST set the "ResolvedRefs" condition to False for this Listener with the + "InvalidRouteKinds" reason. + + Support: Core + items: + description: RouteGroupKind indicates the group and kind + of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: |- + Namespaces indicates namespaces from which Routes may be attached to this + Listener. This is restricted to the namespace of this Gateway by default. + + Support: Core + properties: + from: + default: Same + description: |- + From indicates where Routes will be selected for this Gateway. Possible + values are: + + * All: Routes in all namespaces may be used by this Gateway. + * Selector: Routes in namespaces selected by the selector may be used by + this Gateway. + * Same: Only Routes in the same namespace may be used by this Gateway. + + Support: Core + enum: + - All + - Selector + - Same + type: string + selector: + description: |- + Selector must be specified when From is set to "Selector". In that case, + only Routes in Namespaces matching this Selector will be selected by this + Gateway. This field is ignored for other values of "From". + + Support: Core + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + type: object + type: object + hostname: + description: |- + Hostname specifies the virtual hostname to match for protocol types that + define this concept. When unspecified, all hostnames are matched. This + field is ignored for protocols that don't require hostname based + matching. + + Implementations MUST apply Hostname matching appropriately for each of + the following protocols: + + * TLS: The Listener Hostname MUST match the SNI. + * HTTP: The Listener Hostname MUST match the Host header of the request. + * HTTPS: The Listener Hostname SHOULD match at both the TLS and HTTP + protocol layers as described above. If an implementation does not + ensure that both the SNI and Host header match the Listener hostname, + it MUST clearly document that. + + For HTTPRoute and TLSRoute resources, there is an interaction with the + `spec.hostnames` array. When both listener and route specify hostnames, + there MUST be an intersection between the values for a Route to be + accepted. For more information, refer to the Route specific Hostnames + documentation. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + name: + description: |- + Name is the name of the Listener. This name MUST be unique within a + ListenerSet. + + Name is not required to be unique across a Gateway and ListenerSets. + Routes can attach to a Listener by having a ListenerSet as a parentRef + and setting the SectionName + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: |- + Port is the network port. Multiple listeners may use the + same port, subject to the Listener compatibility rules. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: Protocol specifies the network protocol this listener + expects to receive. + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: |- + TLS is the TLS configuration for the Listener. This field is required if + the Protocol field is "HTTPS" or "TLS". It is invalid to set this field + if the Protocol field is "HTTP", "TCP", or "UDP". + + The association of SNIs to Certificate defined in GatewayTLSConfig is + defined based on the Hostname field for this listener. + + The GatewayClass MUST use the longest matching SNI out of all + available certificates for any TLS handshake. + properties: + certificateRefs: + description: |- + CertificateRefs contains a series of references to Kubernetes objects that + contains TLS certificates and private keys. These certificates are used to + establish a TLS handshake for requests that match the hostname of the + associated listener. + + A single CertificateRef to a Kubernetes Secret has "Core" support. + Implementations MAY choose to support attaching multiple certificates to + a Listener, but this behavior is implementation-specific. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + + This field is required to have at least one element when the mode is set + to "Terminate" (default) and is optional otherwise. + + CertificateRefs can reference to standard Kubernetes resources, i.e. + Secret, or implementation-specific custom resources. + + Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls + + Support: Implementation-specific (More than one reference or other resource types) + items: + description: |- + SecretObjectReference identifies an API object including its namespace, + defaulting to Secret. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example + "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + frontendValidation: + description: |- + FrontendValidation holds configuration information for validating the frontend (client). + Setting this field will require clients to send a client certificate + required for validation during the TLS handshake. In browsers this may result in a dialog appearing + that requests a user to specify the client certificate. + The maximum depth of a certificate chain accepted in verification is Implementation specific. + + Support: Extended + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to + Kubernetes objects that contain TLS certificates of + the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the client. + + A single CA certificate reference to a Kubernetes ConfigMap + has "Core" support. + Implementations MAY choose to support attaching multiple CA certificates to + a Listener, but this behavior is implementation-specific. + + Support: Core - A single reference to a Kubernetes ConfigMap + with the CA certificate in a key named `ca.crt`. + + Support: Implementation-specific (More than one reference, or other kinds + of resources). + + References to a resource in a different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + items: + description: |- + ObjectReference identifies an API object including its namespace. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When set to the empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "ConfigMap" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + maxItems: 8 + minItems: 1 + type: array + type: object + mode: + default: Terminate + description: |- + Mode defines the TLS behavior for the TLS session initiated by the client. + There are two possible modes: + + - Terminate: The TLS session between the downstream client and the + Gateway is terminated at the Gateway. This mode requires certificates + to be specified in some way, such as populating the certificateRefs + field. + - Passthrough: The TLS session is NOT terminated by the Gateway. This + implies that the Gateway can't decipher the TLS stream except for + the ClientHello message of the TLS protocol. The certificateRefs field + is ignored in this mode. + + Support: Core + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Options are a list of key/value pairs to enable extended TLS + configuration for each implementation. For example, configuring the + minimum TLS version or supported cipher suites. + + A set of common keys MAY be defined by the API in the future. To avoid + any ambiguity, implementation-specific definitions MUST use + domain-prefixed names, such as `example.com/my-custom-option`. + Un-prefixed names are reserved for key names defined by Gateway API. + + Support: Implementation-specific + maxProperties: 16 + type: object + type: object + x-kubernetes-validations: + - message: certificateRefs or options must be specified when + mode is Terminate + rule: 'self.mode == ''Terminate'' ? size(self.certificateRefs) + > 0 || size(self.options) > 0 : true' + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: tls must not be specified for protocols ['HTTP', 'TCP', + 'UDP'] + rule: 'self.all(l, l.protocol in [''HTTP'', ''TCP'', ''UDP''] ? + !has(l.tls) : true)' + - message: tls mode must be Terminate for protocol HTTPS + rule: 'self.all(l, (l.protocol == ''HTTPS'' && has(l.tls)) ? (l.tls.mode + == '''' || l.tls.mode == ''Terminate'') : true)' + - message: hostname must not be specified for protocols ['TCP', 'UDP'] + rule: 'self.all(l, l.protocol in [''TCP'', ''UDP''] ? (!has(l.hostname) + || l.hostname == '''') : true)' + - message: Listener name must be unique within the Gateway + rule: self.all(l1, self.exists_one(l2, l1.name == l2.name)) + - message: Combination of port, protocol and hostname must be unique + for each listener + rule: 'self.all(l1, !has(l1.port) || self.exists_one(l2, has(l2.port) + && l1.port == l2.port && l1.protocol == l2.protocol && (has(l1.hostname) + && has(l2.hostname) ? l1.hostname == l2.hostname : !has(l1.hostname) + && !has(l2.hostname))))' + parentRef: + description: ParentRef references the Gateway that the listeners are + attached to. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: Kind is kind of the referent. For example "Gateway". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. If not present, + the namespace of the referent is assumed to be the same as + the namespace of the referring object. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + required: + - listeners + - parentRef + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: Status defines the current state of ListenerSet. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: |- + Conditions describe the current conditions of the ListenerSet. + + Implementations MUST express ListenerSet conditions using the + `ListenerSetConditionType` and `ListenerSetConditionReason` + constants so that operators and tools can converge on a common + vocabulary to describe ListenerSet state. + + Known condition types are: + + * "Accepted" + * "Programmed" + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port + defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: |- + AttachedRoutes represents the total number of Routes that have been + successfully attached to this Listener. + + Successful attachment of a Route to a Listener is based solely on the + combination of the AllowedRoutes field on the corresponding Listener + and the Route's ParentRefs field. A Route is successfully attached to + a Listener when it is selected by the Listener's AllowedRoutes field + AND the Route has a valid ParentRef selecting the whole Gateway + resource or a specific Listener as a parent resource (more detail on + attachment semantics can be found in the documentation on the various + Route kinds ParentRefs fields). Listener or Route status does not impact + successful attachment, i.e. the AttachedRoutes field count MUST be set + for Listeners with condition Accepted: false and MUST count successfully + attached Routes that may themselves have Accepted: false conditions. + + Uses for this field include troubleshooting Route attachment and + measuring blast radius/impact of changes to a Listener. + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this + listener. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status + corresponds to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: Port is the network port the listener is configured + to listen on. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + supportedKinds: + description: |- + SupportedKinds is the list indicating the Kinds supported by this + listener. This MUST represent the kinds an implementation supports for + that Listener configuration. + + If kinds are specified in Spec that are not supported, they MUST NOT + appear in this list and an implementation MUST set the "ResolvedRefs" + condition to "False" with the "InvalidRouteKinds" reason. If both valid + and invalid Route kinds are specified, the implementation MUST + reference the valid Route kinds that have been specified. + items: + description: RouteGroupKind indicates the group and kind of + a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - port + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Copyright 2025 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Gateway API Experimental channel install +# diff --git a/tools/linter/codespell/.codespell.skip b/tools/linter/codespell/.codespell.skip index c39a3ea1d9..91f15efdad 100644 --- a/tools/linter/codespell/.codespell.skip +++ b/tools/linter/codespell/.codespell.skip @@ -13,6 +13,7 @@ go.mod go.sum bin ./charts +./test/helm/* *.js */testdata/*.yaml ./site/public/* diff --git a/tools/linter/yamllint/.yamllint b/tools/linter/yamllint/.yamllint index ed744c2fb2..a75e7b5c44 100644 --- a/tools/linter/yamllint/.yamllint +++ b/tools/linter/yamllint/.yamllint @@ -7,9 +7,11 @@ ignore: | # the Install YAML in bin/ charts/gateway-helm/ charts/gateway-addons-helm/ - charts/gateway-crds-helm + charts/gateway-crds-helm/ bin/install.yaml - test/helm/ + test/helm/gateway-helm/ + test/helm/gateway-addons-helm/ + test/helm/gateway-crds-helm/ examples/extension-server/charts/extension-server site/node_modules/ diff --git a/tools/make/helm.mk b/tools/make/helm.mk index 591619ad37..2dc788de67 100644 --- a/tools/make/helm.mk +++ b/tools/make/helm.mk @@ -73,9 +73,11 @@ helm-generate.%: @for file in $(wildcard test/helm/${CHART_NAME}/*.in.yaml); do \ filename=$$(basename $${file}); \ output="$${filename%.in.*}.out.yaml"; \ - if [ ${CHART_NAME} == "gateway-addons-helm" ]; then \ - helm template ${CHART_NAME} charts/${CHART_NAME} -f $${file} > test/helm/${CHART_NAME}/$$output --namespace=monitoring; \ - else \ + if [ ${CHART_NAME} == "gateway-addons-helm" ]; then \ + helm template ${CHART_NAME} charts/${CHART_NAME} -f $${file} > test/helm/${CHART_NAME}/$$output --namespace=monitoring; \ + elif [ ${CHART_NAME} == "gateway-crds-helm" ]; then \ + helm template ${CHART_NAME} charts/${CHART_NAME} -f $${file} > test/helm/${CHART_NAME}/$$output; \ + else \ helm template ${CHART_NAME} charts/${CHART_NAME} -f $${file} > test/helm/${CHART_NAME}/$$output --namespace=envoy-gateway-system; \ fi; \ done From c89ecb0d152c3f3a952b43b70023643d87306ae5 Mon Sep 17 00:00:00 2001 From: Sudipto Baral Date: Fri, 2 May 2025 15:37:05 -0400 Subject: [PATCH 06/66] =?UTF-8?q?Add=20seed=20corpus=20to=20guide=20the=20?= =?UTF-8?q?fuzzer=20to=20generate=20combinations=20of=20gatew=E2=80=A6=20(?= =?UTF-8?q?#5904)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add seed corpus to guide the fuzzer to generate combinations of gateway resources. Signed-off-by: sudipto baral Signed-off-by: Arko Dasgupta --- test/fuzz/oss_fuzz_build.sh | 4 +- .../fuzz/testdata/FuzzGatewayAPIToXDS/backend | 9 ++++ .../FuzzGatewayAPIToXDS/backend_tls_policy | 12 +++++ .../testdata/FuzzGatewayAPIToXDS/configmap | 10 ++++ .../testdata/FuzzGatewayAPIToXDS/envoyproxy | 12 +++++ .../FuzzGatewayAPIToXDS/filter_and_extension | 30 +++++++++++ .../fuzz/testdata/FuzzGatewayAPIToXDS/gateway | 17 ++++++ .../FuzzGatewayAPIToXDS/gateway_and_grpcroute | 52 +++++++++++++++++++ .../FuzzGatewayAPIToXDS/gateway_and_httproute | 44 ++++++++++++++++ .../FuzzGatewayAPIToXDS/gateway_and_tcproute | 43 +++++++++++++++ .../FuzzGatewayAPIToXDS/gateway_and_udproute | 43 +++++++++++++++ .../testdata/FuzzGatewayAPIToXDS/grpcroute | 22 ++++++++ .../testdata/FuzzGatewayAPIToXDS/httproute | 14 +++++ .../testdata/FuzzGatewayAPIToXDS/patch_policy | 29 +++++++++++ .../FuzzGatewayAPIToXDS/reference_grant | 22 ++++++++ test/fuzz/testdata/FuzzGatewayAPIToXDS/secret | 34 ++++++++++++ .../FuzzGatewayAPIToXDS/security_policy | 20 +++++++ .../testdata/FuzzGatewayAPIToXDS/tcproute | 13 +++++ .../FuzzGatewayAPIToXDS/traffic_policy | 47 +++++++++++++++++ .../testdata/FuzzGatewayAPIToXDS/udproute | 13 +++++ test/fuzz/xds_fuzz_test.go | 27 ---------- 21 files changed, 489 insertions(+), 28 deletions(-) create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/backend create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/backend_tls_policy create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/configmap create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/envoyproxy create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/filter_and_extension create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway_and_grpcroute create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway_and_httproute create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway_and_tcproute create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway_and_udproute create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/grpcroute create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/httproute create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/patch_policy create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/reference_grant create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/secret create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/security_policy create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/tcproute create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/traffic_policy create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/udproute diff --git a/test/fuzz/oss_fuzz_build.sh b/test/fuzz/oss_fuzz_build.sh index a85878350d..854ed9393d 100755 --- a/test/fuzz/oss_fuzz_build.sh +++ b/test/fuzz/oss_fuzz_build.sh @@ -34,4 +34,6 @@ go mod tidy # compile native-format fuzzers compile_native_go_fuzzer github.com/envoyproxy/gateway/test/fuzz FuzzGatewayAPIToXDS FuzzGatewayAPIToXDS -compile_native_go_fuzzer github.com/envoyproxy/gateway/test/fuzz FuzzGatewayClassToXDS FuzzGatewayClassToXDS + +# add seed corpus +zip -j $OUT/FuzzGatewayAPIToXDS_seed_corpus.zip "$SRC"/gateway/test/fuzz/testdata/FuzzGatewayAPIToXDS/* diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/backend b/test/fuzz/testdata/FuzzGatewayAPIToXDS/backend new file mode 100644 index 0000000000..bcaa3347cf --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/backend @@ -0,0 +1,9 @@ +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: Backend +metadata: + name: backend +spec: + endpoints: + - ip: + address: 0.0.0.0 + port: 4321 diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/backend_tls_policy b/test/fuzz/testdata/FuzzGatewayAPIToXDS/backend_tls_policy new file mode 100644 index 0000000000..b3d572025a --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/backend_tls_policy @@ -0,0 +1,12 @@ +apiVersion: gateway.networking.k8s.io/v1alpha3 +kind: BackendTLSPolicy +metadata: + name: example-tls-policy +spec: + targetRefs: + - group: 'gateway.envoyproxy.io' + kind: Backend + name: backend + validation: + wellKnownCACertificates: "System" + hostname: www.example.com diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/configmap b/test/fuzz/testdata/FuzzGatewayAPIToXDS/configmap new file mode 100644 index 0000000000..e19bde2ccb --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/configmap @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: configmap + namespace: default +data: + player_initial_lives: "3" + game.properties: | + enemy.types=aliens,monsters + player.maximum-lives=5 diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/envoyproxy b/test/fuzz/testdata/FuzzGatewayAPIToXDS/envoyproxy new file mode 100644 index 0000000000..23dc91e6f7 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/envoyproxy @@ -0,0 +1,12 @@ +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: EnvoyProxy +metadata: + name: example + namespace: default +spec: + provider: + type: Kubernetes + kubernetes: + envoyService: + annotations: + custom1: svc-annotation1 diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/filter_and_extension b/test/fuzz/testdata/FuzzGatewayAPIToXDS/filter_and_extension new file mode 100644 index 0000000000..ff34c2de34 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/filter_and_extension @@ -0,0 +1,30 @@ +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: HTTPRouteFilter +metadata: + name: direct-response-inline + namespace: default +spec: + directResponse: + contentType: text/plain + body: + type: Inline + inline: "OK" +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: EnvoyExtensionPolicy +metadata: + name: ext-proc-example +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: backend + extProc: + - backendRefs: + - name: grpc-ext-proc + port: 9002 + processingMode: + request: {} + response: + body: Streamed diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway b/test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway new file mode 100644 index 0000000000..5cd4835291 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway @@ -0,0 +1,17 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: GatewayClass +metadata: + name: eg +spec: + controllerName: gateway.envoyproxy.io/gatewayclass-controller +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: eg +spec: + gatewayClassName: eg + listeners: + - name: http + protocol: HTTP + port: 80 diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway_and_grpcroute b/test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway_and_grpcroute new file mode 100644 index 0000000000..af3477bf75 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway_and_grpcroute @@ -0,0 +1,52 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: GatewayClass +metadata: + name: eg +spec: + controllerName: gateway.envoyproxy.io/gatewayclass-controller +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: eg + namespace: default +spec: + gatewayClassName: eg + listeners: + - name: http + protocol: HTTP + port: 80 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: GRPCRoute +metadata: + name: backend + namespace: default +spec: + parentRefs: + - name: eg + sectionName: grpc + hostnames: + - "www.grpc-example.com" + rules: + - matches: + - method: + service: com.example.Things + method: DoThing + headers: + - name: com.example.Header + value: foobar + backendRefs: + - name: providedBackend + port: 9000 +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: Backend +metadata: + name: providedBackend + namespace: default +spec: + endpoints: + - ip: + address: 0.0.0.0 + port: 8000 diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway_and_httproute b/test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway_and_httproute new file mode 100644 index 0000000000..eb6b97651a --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway_and_httproute @@ -0,0 +1,44 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: GatewayClass +metadata: + name: eg +spec: + controllerName: gateway.envoyproxy.io/gatewayclass-controller +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: eg + namespace: default +spec: + gatewayClassName: eg + listeners: + - name: http + protocol: HTTP + port: 80 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: backend + namespace: default +spec: + parentRefs: + - name: eg + hostnames: + - "www.example.com" + rules: + - backendRefs: + - name: providedBackend + port: 8000 +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: Backend +metadata: + name: providedBackend + namespace: default +spec: + endpoints: + - ip: + address: 0.0.0.0 + port: 8000 diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway_and_tcproute b/test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway_and_tcproute new file mode 100644 index 0000000000..abff21f8de --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway_and_tcproute @@ -0,0 +1,43 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: GatewayClass +metadata: + name: eg +spec: + controllerName: gateway.envoyproxy.io/gatewayclass-controller +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: eg + namespace: default +spec: + gatewayClassName: eg + listeners: + - name: tcp + protocol: TCP + port: 3000 +--- +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: TCPRoute +metadata: + name: backend + namespace: default +spec: + parentRefs: + - name: eg + sectionName: tcp + rules: + - backendRefs: + - name: backend + port: 3000 +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: Backend +metadata: + name: backend + namespace: default +spec: + endpoints: + - ip: + address: 0.0.0.0 + port: 3000 diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway_and_udproute b/test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway_and_udproute new file mode 100644 index 0000000000..9b8642c146 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway_and_udproute @@ -0,0 +1,43 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: GatewayClass +metadata: + name: eg +spec: + controllerName: gateway.envoyproxy.io/gatewayclass-controller +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: eg + namespace: default +spec: + gatewayClassName: eg + listeners: + - name: http + protocol: HTTP + port: 80 +--- +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: UDPRoute +metadata: + name: backend + namespace: default +spec: + parentRefs: + - name: eg + sectionName: udp + rules: + - backendRefs: + - name: backend + port: 3000 +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: Backend +metadata: + name: providedBackend + namespace: default +spec: + endpoints: + - ip: + address: 0.0.0.0 + port: 8000 diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/grpcroute b/test/fuzz/testdata/FuzzGatewayAPIToXDS/grpcroute new file mode 100644 index 0000000000..dfb260f20f --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/grpcroute @@ -0,0 +1,22 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: GRPCRoute +metadata: + name: backend + namespace: default +spec: + parentRefs: + - name: eg + sectionName: grpc + hostnames: + - "www.grpc-example.com" + rules: + - matches: + - method: + service: com.example.Things + method: DoThing + headers: + - name: com.example.Header + value: foobar + backendRefs: + - name: providedBackend + port: 9000 \ No newline at end of file diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/httproute b/test/fuzz/testdata/FuzzGatewayAPIToXDS/httproute new file mode 100644 index 0000000000..5fd38114fb --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/httproute @@ -0,0 +1,14 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: backend + namespace: default +spec: + parentRefs: + - name: eg + hostnames: + - "www.example.com" + rules: + - backendRefs: + - name: providedBackend + port: 8000 \ No newline at end of file diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/patch_policy b/test/fuzz/testdata/FuzzGatewayAPIToXDS/patch_policy new file mode 100644 index 0000000000..1ff9370846 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/patch_policy @@ -0,0 +1,29 @@ +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: EnvoyPatchPolicy +metadata: + name: ratelimit-patch-policy + namespace: default +spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: eg + type: JSONPatch + jsonPatches: + - type: "type.googleapis.com/envoy.config.listener.v3.Listener" + name: default/eg/http + operation: + op: add + path: "/default_filter_chain/filters/0/typed_config/http_filters/0" + value: + name: "envoy.filters.http.ratelimit" + typed_config: + "@type": "type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit" + domain: "eag-ratelimit" + failure_mode_deny: true + timeout: 1s + rate_limit_service: + grpc_service: + envoy_grpc: + cluster_name: rate-limit-cluster + transport_api_version: V3 diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/reference_grant b/test/fuzz/testdata/FuzzGatewayAPIToXDS/reference_grant new file mode 100644 index 0000000000..3a6dbc4455 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/reference_grant @@ -0,0 +1,22 @@ +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: ReferenceGrant +metadata: + name: refg-example + namespace: default +spec: + from: + - group: gateway.networking.k8s.io + kind: HTTPRoute + namespace: envoy-gateway + - group: gateway.networking.k8s.io + kind: TCPRoute + namespace: envoy-gateway + - group: gateway.networking.k8s.io + kind: Gateway + namespace: envoy-gateway + - group: gateway.networking.k8s.io + kind: BackendTLSPolicy + namespace: default + to: + - group: "" + kind: Service diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/secret b/test/fuzz/testdata/FuzzGatewayAPIToXDS/secret new file mode 100644 index 0000000000..6824f7002e --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/secret @@ -0,0 +1,34 @@ +apiVersion: v1 +kind: Secret +metadata: + name: secret-with-data-and-string-data + namespace: default +data: + .secret-file: dmFsdWUtMg0KDQo= +stringData: + secret: "literal value" +--- +apiVersion: v1 +kind: Secret +metadata: + name: secret-with-data + namespace: default +data: + .secret-file: dmFsdWUtMg0KDQo= +--- +apiVersion: v1 +kind: Secret +metadata: + name: secret-with-string-data + namespace: default +stringData: + secret: "literal value" +--- +apiVersion: v1 +kind: Secret +metadata: + name: secret-with-type + namespace: default +type: "type value" +data: + .secret-file: dmFsdWUtMg0KDQo= diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/security_policy b/test/fuzz/testdata/FuzzGatewayAPIToXDS/security_policy new file mode 100644 index 0000000000..c2d0943612 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/security_policy @@ -0,0 +1,20 @@ +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: jwt-example +spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: backend + apiKeyAuth: + credentialRefs: + - name: foobar + extractFrom: + - headers: + - foobar + jwt: + providers: + - name: example + remoteJWKS: + uri: https://raw.githubusercontent.com/envoyproxy/gateway/main/examples/kubernetes/jwt/jwks.json diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/tcproute b/test/fuzz/testdata/FuzzGatewayAPIToXDS/tcproute new file mode 100644 index 0000000000..1ba7b45909 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/tcproute @@ -0,0 +1,13 @@ +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: TCPRoute +metadata: + name: backend + namespace: default +spec: + parentRefs: + - name: eg + sectionName: tcp + rules: + - backendRefs: + - name: backend + port: 3000 \ No newline at end of file diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/traffic_policy b/test/fuzz/testdata/FuzzGatewayAPIToXDS/traffic_policy new file mode 100644 index 0000000000..6cb209e5f1 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/traffic_policy @@ -0,0 +1,47 @@ +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: cookie-lb-policy + namespace: gateway-conformance-infra +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: cookie-lb-route + loadBalancer: + type: ConsistentHash + consistentHash: + type: Cookie + cookie: + name: "Lb-Test-Cookie" + ttl: 60s + attributes: + SameSite: Strict + retry: + retryOn: + httpStatusCodes: + - 200 + - 404 + healthCheck: + active: + type: HTTP + http: + path: "/" + method: GET + circuitBreaker: + maxRequestsPerConnection: 123 +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: ClientTrafficPolicy +metadata: + name: client-timeout + namespace: gateway-conformance-infra +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: same-namespace + timeout: + http: + requestReceivedTimeout: 50ms diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/udproute b/test/fuzz/testdata/FuzzGatewayAPIToXDS/udproute new file mode 100644 index 0000000000..9dea136e87 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/udproute @@ -0,0 +1,13 @@ +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: UDPRoute +metadata: + name: backend + namespace: default +spec: + parentRefs: + - name: eg + sectionName: udp + rules: + - backendRefs: + - name: backend + port: 3000 \ No newline at end of file diff --git a/test/fuzz/xds_fuzz_test.go b/test/fuzz/xds_fuzz_test.go index 982198e2b5..1dd549e3e5 100644 --- a/test/fuzz/xds_fuzz_test.go +++ b/test/fuzz/xds_fuzz_test.go @@ -9,8 +9,6 @@ import ( "testing" fuzz "github.com/AdaLogics/go-fuzz-headers" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" "github.com/envoyproxy/gateway/internal/cmd/egctl" "github.com/envoyproxy/gateway/internal/gatewayapi/resource" @@ -30,28 +28,3 @@ func FuzzGatewayAPIToXDS(f *testing.F) { _, _ = egctl.TranslateGatewayAPIToXds(namespace, dnsDomain, resourceType, rs) }) } - -func FuzzGatewayClassToXDS(f *testing.F) { - f.Fuzz(func(t *testing.T, b []byte) { - fc := fuzz.NewConsumer(b) - namespace, _ := fc.GetString() - name, _ := fc.GetString() - controllerName, _ := fc.GetString() - dnsDomain, _ := fc.GetString() - resourceType, _ := fc.GetString() - - rs := &resource.Resources{ - GatewayClass: &gwapiv1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: gwapiv1.GatewayClassSpec{ - ControllerName: gwapiv1.GatewayController(controllerName), - }, - }, - } - - _, _ = egctl.TranslateGatewayAPIToXds(namespace, dnsDomain, resourceType, rs) - }) -} From ed4d4cc06efa31a0c91b0172b5694ed08973e3d1 Mon Sep 17 00:00:00 2001 From: hansselvig <34341538+hansselvig@users.noreply.github.com> Date: Sat, 3 May 2025 23:08:07 +0200 Subject: [PATCH 07/66] fix(chart): passing root context to template (#5902) * chore: passing root context to template Signed-off-by: hansselvig <34341538+hansselvig@users.noreply.github.com> Signed-off-by: Arko Dasgupta --- .../templates/envoy-gateway-rbac.yaml | 2 +- ...ay-gateway-namespace-config-watch.out.yaml | 1013 +++++++++++++++++ 2 files changed, 1014 insertions(+), 1 deletion(-) diff --git a/charts/gateway-helm/templates/envoy-gateway-rbac.yaml b/charts/gateway-helm/templates/envoy-gateway-rbac.yaml index 5d975b8aaa..e07c25f9a3 100644 --- a/charts/gateway-helm/templates/envoy-gateway-rbac.yaml +++ b/charts/gateway-helm/templates/envoy-gateway-rbac.yaml @@ -19,7 +19,7 @@ metadata: name: {{ include "eg.fullname" $ }}-envoy-gateway-role namespace: {{ $ns | quote }} rules: -{{ include "eg.rbac.namespaced" . }} +{{ include "eg.rbac.namespaced" $ }} --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/test/helm/gateway-helm/envoy-gateway-gateway-namespace-config-watch.out.yaml b/test/helm/gateway-helm/envoy-gateway-gateway-namespace-config-watch.out.yaml index e69de29bb2..a288660e2f 100644 --- a/test/helm/gateway-helm/envoy-gateway-gateway-namespace-config-watch.out.yaml +++ b/test/helm/gateway-helm/envoy-gateway-gateway-namespace-config-watch.out.yaml @@ -0,0 +1,1013 @@ +--- +# Source: gateway-helm/templates/envoy-gateway-serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: envoy-gateway + namespace: 'envoy-gateway-system' + labels: + helm.sh/chart: gateway-helm-v0.0.0-latest + app.kubernetes.io/name: gateway-helm + app.kubernetes.io/instance: gateway-helm + app.kubernetes.io/version: "latest" + app.kubernetes.io/managed-by: Helm +--- +# Source: gateway-helm/templates/envoy-gateway-config.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: envoy-gateway-config + namespace: 'envoy-gateway-system' + labels: + helm.sh/chart: gateway-helm-v0.0.0-latest + app.kubernetes.io/name: gateway-helm + app.kubernetes.io/instance: gateway-helm + app.kubernetes.io/version: "latest" + app.kubernetes.io/managed-by: Helm +data: + envoy-gateway.yaml: | + apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyGateway + extensionApis: {} + gateway: + controllerName: gateway.envoyproxy.io/gatewayclass-controller + logging: + level: + default: info + provider: + kubernetes: + deploy: + type: GatewayNamespace + rateLimitDeployment: + container: + image: docker.io/envoyproxy/ratelimit:master + patch: + type: StrategicMerge + value: + spec: + template: + spec: + containers: + - imagePullPolicy: IfNotPresent + name: envoy-ratelimit + shutdownManager: + image: docker.io/envoyproxy/gateway-dev:latest + watch: + namespaces: + - default + - app-ns + type: Kubernetes +--- +# Source: gateway-helm/templates/envoy-gateway-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: gateway-helm-envoy-gateway-role +rules: +- apiGroups: + - "" + resources: + - nodes + - namespaces + verbs: + - get + - list + - watch +- apiGroups: + - gateway.networking.k8s.io + resources: + - gatewayclasses + verbs: + - get + - list + - patch + - update + - watch +- apiGroups: + - gateway.networking.k8s.io + resources: + - gatewayclasses/status + verbs: + - update +- apiGroups: + - multicluster.x-k8s.io + resources: + - serviceimports + verbs: + - get + - list + - watch +--- +# Source: gateway-helm/templates/namespaced-infra-manager-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: gateway-helm-infra-manager-tokenreview + labels: + helm.sh/chart: gateway-helm-v0.0.0-latest + app.kubernetes.io/name: gateway-helm + app.kubernetes.io/instance: gateway-helm + app.kubernetes.io/version: "latest" + app.kubernetes.io/managed-by: Helm +rules: +- apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create +--- +# Source: gateway-helm/templates/envoy-gateway-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: gateway-helm-envoy-gateway-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: gateway-helm-envoy-gateway-role +subjects: +- kind: ServiceAccount + name: 'envoy-gateway' + namespace: 'envoy-gateway-system' +--- +# Source: gateway-helm/templates/namespaced-infra-manager-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: gateway-helm-infra-manager-tokenreview + labels: + helm.sh/chart: gateway-helm-v0.0.0-latest + app.kubernetes.io/name: gateway-helm + app.kubernetes.io/instance: gateway-helm + app.kubernetes.io/version: "latest" + app.kubernetes.io/managed-by: Helm +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: 'gateway-helm-infra-manager-tokenreview' +subjects: +- kind: ServiceAccount + name: 'envoy-gateway' + namespace: 'envoy-gateway-system' +--- +# Source: gateway-helm/templates/envoy-gateway-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + creationTimestamp: null + name: gateway-helm-envoy-gateway-role + namespace: "default" +rules: +- apiGroups: + - "" + resources: + - configmaps + - secrets + - services + verbs: + - get + - list + - watch +- apiGroups: + - apps + resources: + - deployments + - daemonsets + verbs: + - get + - list + - watch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - get + - list + - watch +- apiGroups: + - gateway.envoyproxy.io + resources: + - envoyproxies + - envoypatchpolicies + - clienttrafficpolicies + - backendtrafficpolicies + - securitypolicies + - envoyextensionpolicies + - backends + - httproutefilters + verbs: + - get + - list + - watch +- apiGroups: + - gateway.envoyproxy.io + resources: + - envoypatchpolicies/status + - clienttrafficpolicies/status + - backendtrafficpolicies/status + - securitypolicies/status + - envoyextensionpolicies/status + - backends/status + verbs: + - update +- apiGroups: + - gateway.networking.k8s.io + resources: + - gateways + - grpcroutes + - httproutes + - referencegrants + - tcproutes + - tlsroutes + - udproutes + - backendtlspolicies + verbs: + - get + - list + - watch +- apiGroups: + - gateway.networking.k8s.io + resources: + - gateways/status + - grpcroutes/status + - httproutes/status + - tcproutes/status + - tlsroutes/status + - udproutes/status + - backendtlspolicies/status + verbs: + - update +- apiGroups: + - "" + resources: + - pods + - pods/binding + verbs: + - get + - list + - patch + - update + - watch +--- +# Source: gateway-helm/templates/envoy-gateway-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + creationTimestamp: null + name: gateway-helm-envoy-gateway-role + namespace: "app-ns" +rules: +- apiGroups: + - "" + resources: + - configmaps + - secrets + - services + verbs: + - get + - list + - watch +- apiGroups: + - apps + resources: + - deployments + - daemonsets + verbs: + - get + - list + - watch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - get + - list + - watch +- apiGroups: + - gateway.envoyproxy.io + resources: + - envoyproxies + - envoypatchpolicies + - clienttrafficpolicies + - backendtrafficpolicies + - securitypolicies + - envoyextensionpolicies + - backends + - httproutefilters + verbs: + - get + - list + - watch +- apiGroups: + - gateway.envoyproxy.io + resources: + - envoypatchpolicies/status + - clienttrafficpolicies/status + - backendtrafficpolicies/status + - securitypolicies/status + - envoyextensionpolicies/status + - backends/status + verbs: + - update +- apiGroups: + - gateway.networking.k8s.io + resources: + - gateways + - grpcroutes + - httproutes + - referencegrants + - tcproutes + - tlsroutes + - udproutes + - backendtlspolicies + verbs: + - get + - list + - watch +- apiGroups: + - gateway.networking.k8s.io + resources: + - gateways/status + - grpcroutes/status + - httproutes/status + - tcproutes/status + - tlsroutes/status + - udproutes/status + - backendtlspolicies/status + verbs: + - update +- apiGroups: + - "" + resources: + - pods + - pods/binding + verbs: + - get + - list + - patch + - update + - watch +--- +# Source: gateway-helm/templates/infra-manager-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: gateway-helm-infra-manager + namespace: 'envoy-gateway-system' + labels: + helm.sh/chart: gateway-helm-v0.0.0-latest + app.kubernetes.io/name: gateway-helm + app.kubernetes.io/instance: gateway-helm + app.kubernetes.io/version: "latest" + app.kubernetes.io/managed-by: Helm +rules: +- apiGroups: + - "" + resources: + - serviceaccounts + - services + - configmaps + verbs: + - create + - get + - delete + - deletecollection + - patch +- apiGroups: + - apps + resources: + - deployments + - daemonsets + verbs: + - create + - get + - delete + - deletecollection + - patch +- apiGroups: + - autoscaling + - policy + resources: + - horizontalpodautoscalers + - poddisruptionbudgets + verbs: + - create + - get + - delete + - deletecollection + - patch +--- +# Source: gateway-helm/templates/leader-election-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: gateway-helm-leader-election-role + namespace: 'envoy-gateway-system' + labels: + helm.sh/chart: gateway-helm-v0.0.0-latest + app.kubernetes.io/name: gateway-helm + app.kubernetes.io/instance: gateway-helm + app.kubernetes.io/version: "latest" + app.kubernetes.io/managed-by: Helm +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +--- +# Source: gateway-helm/templates/namespaced-infra-manager-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: gateway-helm-namespaced-infra-manager + namespace: "default" + labels: + helm.sh/chart: gateway-helm-v0.0.0-latest + app.kubernetes.io/name: gateway-helm + app.kubernetes.io/instance: gateway-helm + app.kubernetes.io/version: "latest" + app.kubernetes.io/managed-by: Helm +rules: +- apiGroups: + - "" + resources: + - serviceaccounts + - services + - configmaps + verbs: + - create + - get + - delete + - deletecollection + - patch +- apiGroups: + - apps + resources: + - deployments + - daemonsets + verbs: + - create + - get + - delete + - deletecollection + - patch +- apiGroups: + - autoscaling + - policy + resources: + - horizontalpodautoscalers + - poddisruptionbudgets + verbs: + - create + - get + - delete + - deletecollection + - patch +--- +# Source: gateway-helm/templates/namespaced-infra-manager-rbac.yaml +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: gateway-helm-namespaced-infra-manager + namespace: "app-ns" + labels: + helm.sh/chart: gateway-helm-v0.0.0-latest + app.kubernetes.io/name: gateway-helm + app.kubernetes.io/instance: gateway-helm + app.kubernetes.io/version: "latest" + app.kubernetes.io/managed-by: Helm +rules: +- apiGroups: + - "" + resources: + - serviceaccounts + - services + - configmaps + verbs: + - create + - get + - delete + - deletecollection + - patch +- apiGroups: + - apps + resources: + - deployments + - daemonsets + verbs: + - create + - get + - delete + - deletecollection + - patch +- apiGroups: + - autoscaling + - policy + resources: + - horizontalpodautoscalers + - poddisruptionbudgets + verbs: + - create + - get + - delete + - deletecollection + - patch +--- +# Source: gateway-helm/templates/envoy-gateway-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: gateway-helm-envoy-gateway-rolebinding + namespace: "default" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: gateway-helm-envoy-gateway-role +subjects: +- kind: ServiceAccount + name: 'envoy-gateway' + namespace: 'envoy-gateway-system' +--- +# Source: gateway-helm/templates/envoy-gateway-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: gateway-helm-envoy-gateway-rolebinding + namespace: "app-ns" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: gateway-helm-envoy-gateway-role +subjects: +- kind: ServiceAccount + name: 'envoy-gateway' + namespace: 'envoy-gateway-system' +--- +# Source: gateway-helm/templates/infra-manager-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: gateway-helm-infra-manager + namespace: 'envoy-gateway-system' + labels: + helm.sh/chart: gateway-helm-v0.0.0-latest + app.kubernetes.io/name: gateway-helm + app.kubernetes.io/instance: gateway-helm + app.kubernetes.io/version: "latest" + app.kubernetes.io/managed-by: Helm +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: 'gateway-helm-infra-manager' +subjects: +- kind: ServiceAccount + name: 'envoy-gateway' + namespace: 'envoy-gateway-system' +--- +# Source: gateway-helm/templates/leader-election-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: gateway-helm-leader-election-rolebinding + namespace: 'envoy-gateway-system' + labels: + helm.sh/chart: gateway-helm-v0.0.0-latest + app.kubernetes.io/name: gateway-helm + app.kubernetes.io/instance: gateway-helm + app.kubernetes.io/version: "latest" + app.kubernetes.io/managed-by: Helm +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: 'gateway-helm-leader-election-role' +subjects: +- kind: ServiceAccount + name: 'envoy-gateway' + namespace: 'envoy-gateway-system' +--- +# Source: gateway-helm/templates/namespaced-infra-manager-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: gateway-helm-namespaced-infra-manager + namespace: "default" + labels: + helm.sh/chart: gateway-helm-v0.0.0-latest + app.kubernetes.io/name: gateway-helm + app.kubernetes.io/instance: gateway-helm + app.kubernetes.io/version: "latest" + app.kubernetes.io/managed-by: Helm +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: 'gateway-helm-namespaced-infra-manager' +subjects: +- kind: ServiceAccount + name: 'envoy-gateway' + namespace: 'envoy-gateway-system' +--- +# Source: gateway-helm/templates/namespaced-infra-manager-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: gateway-helm-namespaced-infra-manager + namespace: "app-ns" + labels: + helm.sh/chart: gateway-helm-v0.0.0-latest + app.kubernetes.io/name: gateway-helm + app.kubernetes.io/instance: gateway-helm + app.kubernetes.io/version: "latest" + app.kubernetes.io/managed-by: Helm +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: 'gateway-helm-namespaced-infra-manager' +subjects: +- kind: ServiceAccount + name: 'envoy-gateway' + namespace: 'envoy-gateway-system' +--- +# Source: gateway-helm/templates/envoy-gateway-service.yaml +apiVersion: v1 +kind: Service +metadata: + name: envoy-gateway + namespace: 'envoy-gateway-system' + labels: + control-plane: envoy-gateway + helm.sh/chart: gateway-helm-v0.0.0-latest + app.kubernetes.io/name: gateway-helm + app.kubernetes.io/instance: gateway-helm + app.kubernetes.io/version: "latest" + app.kubernetes.io/managed-by: Helm +spec: + selector: + control-plane: envoy-gateway + app.kubernetes.io/name: gateway-helm + app.kubernetes.io/instance: gateway-helm + ports: + - name: grpc + port: 18000 + targetPort: 18000 + - name: ratelimit + port: 18001 + targetPort: 18001 + - name: wasm + port: 18002 + targetPort: 18002 + - name: metrics + port: 19001 + targetPort: 19001 + - name: webhook + port: 9443 + targetPort: 9443 +--- +# Source: gateway-helm/templates/envoy-gateway-deployment.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: envoy-gateway + namespace: 'envoy-gateway-system' + labels: + control-plane: envoy-gateway + helm.sh/chart: gateway-helm-v0.0.0-latest + app.kubernetes.io/name: gateway-helm + app.kubernetes.io/instance: gateway-helm + app.kubernetes.io/version: "latest" + app.kubernetes.io/managed-by: Helm +spec: + replicas: 1 + selector: + matchLabels: + control-plane: envoy-gateway + app.kubernetes.io/name: gateway-helm + app.kubernetes.io/instance: gateway-helm + template: + metadata: + annotations: + prometheus.io/port: "19001" + prometheus.io/scrape: "true" + labels: + control-plane: envoy-gateway + app.kubernetes.io/name: gateway-helm + app.kubernetes.io/instance: gateway-helm + spec: + containers: + - args: + - server + - --config-path=/config/envoy-gateway.yaml + env: + - name: ENVOY_GATEWAY_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + - name: KUBERNETES_CLUSTER_DOMAIN + value: cluster.local + image: docker.io/envoyproxy/gateway-dev:latest + imagePullPolicy: Always + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + name: envoy-gateway + ports: + - containerPort: 18000 + name: grpc + - containerPort: 18001 + name: ratelimit + - containerPort: 18002 + name: wasm + - containerPort: 19001 + name: metrics + - name: webhook + containerPort: 9443 + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + limits: + memory: 1024Mi + requests: + cpu: 100m + memory: 256Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + runAsGroup: 65532 + runAsNonRoot: true + runAsUser: 65532 + seccompProfile: + type: RuntimeDefault + volumeMounts: + - mountPath: /config + name: envoy-gateway-config + readOnly: true + - mountPath: /certs + name: certs + readOnly: true + imagePullSecrets: [] + serviceAccountName: envoy-gateway + terminationGracePeriodSeconds: 10 + volumes: + - configMap: + defaultMode: 420 + name: envoy-gateway-config + name: envoy-gateway-config + - name: certs + secret: + secretName: envoy-gateway +--- +# Source: gateway-helm/templates/certgen-rbac.yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: gateway-helm-certgen + namespace: 'envoy-gateway-system' + labels: + helm.sh/chart: gateway-helm-v0.0.0-latest + app.kubernetes.io/name: gateway-helm + app.kubernetes.io/instance: gateway-helm + app.kubernetes.io/version: "latest" + app.kubernetes.io/managed-by: Helm + annotations: + "helm.sh/hook": pre-install, pre-upgrade + "helm.sh/hook-weight": "-1" # Ensure rbac is created before the certgen job when using ArgoCD. +--- +# Source: gateway-helm/templates/certgen-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: 'gateway-helm-certgen:envoy-gateway-system' + labels: + helm.sh/chart: gateway-helm-v0.0.0-latest + app.kubernetes.io/name: gateway-helm + app.kubernetes.io/instance: gateway-helm + app.kubernetes.io/version: "latest" + app.kubernetes.io/managed-by: Helm + annotations: + "helm.sh/hook": pre-install, pre-upgrade + "helm.sh/hook-weight": "-1" # Ensure rbac is created before the certgen job when using ArgoCD. +rules: + - apiGroups: + - admissionregistration.k8s.io + resources: + - mutatingwebhookconfigurations + verbs: + - get + - list + - watch + - apiGroups: + - admissionregistration.k8s.io + resources: + - mutatingwebhookconfigurations + resourceNames: + - 'envoy-gateway-topology-injector.envoy-gateway-system' + verbs: + - update + - patch +--- +# Source: gateway-helm/templates/certgen-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: 'gateway-helm-certgen:envoy-gateway-system' + labels: + helm.sh/chart: gateway-helm-v0.0.0-latest + app.kubernetes.io/name: gateway-helm + app.kubernetes.io/instance: gateway-helm + app.kubernetes.io/version: "latest" + app.kubernetes.io/managed-by: Helm + annotations: + "helm.sh/hook": pre-install, pre-upgrade + "helm.sh/hook-weight": "-1" # Ensure rbac is created before the certgen job when using ArgoCD. +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: 'gateway-helm-certgen:envoy-gateway-system' +subjects: + - kind: ServiceAccount + name: 'gateway-helm-certgen' + namespace: 'envoy-gateway-system' +--- +# Source: gateway-helm/templates/certgen-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: gateway-helm-certgen + namespace: 'envoy-gateway-system' + labels: + helm.sh/chart: gateway-helm-v0.0.0-latest + app.kubernetes.io/name: gateway-helm + app.kubernetes.io/instance: gateway-helm + app.kubernetes.io/version: "latest" + app.kubernetes.io/managed-by: Helm + annotations: + "helm.sh/hook": pre-install, pre-upgrade + "helm.sh/hook-weight": "-1" # Ensure rbac is created before the certgen job when using ArgoCD. +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - create + - update +--- +# Source: gateway-helm/templates/certgen-rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: gateway-helm-certgen + namespace: 'envoy-gateway-system' + labels: + helm.sh/chart: gateway-helm-v0.0.0-latest + app.kubernetes.io/name: gateway-helm + app.kubernetes.io/instance: gateway-helm + app.kubernetes.io/version: "latest" + app.kubernetes.io/managed-by: Helm + annotations: + "helm.sh/hook": pre-install, pre-upgrade + "helm.sh/hook-weight": "-1" # Ensure rbac is created before the certgen job when using ArgoCD. +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: 'gateway-helm-certgen' +subjects: +- kind: ServiceAccount + name: 'gateway-helm-certgen' + namespace: 'envoy-gateway-system' +--- +# Source: gateway-helm/templates/certgen.yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: gateway-helm-certgen + namespace: 'envoy-gateway-system' + labels: + helm.sh/chart: gateway-helm-v0.0.0-latest + app.kubernetes.io/name: gateway-helm + app.kubernetes.io/instance: gateway-helm + app.kubernetes.io/version: "latest" + app.kubernetes.io/managed-by: Helm + annotations: + "helm.sh/hook": pre-install, pre-upgrade +spec: + backoffLimit: 1 + completions: 1 + parallelism: 1 + template: + metadata: + labels: + app: certgen + spec: + containers: + - command: + - envoy-gateway + - certgen + env: + - name: ENVOY_GATEWAY_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + - name: KUBERNETES_CLUSTER_DOMAIN + value: cluster.local + image: docker.io/envoyproxy/gateway-dev:latest + imagePullPolicy: Always + name: envoy-gateway-certgen + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + runAsGroup: 65532 + runAsNonRoot: true + runAsUser: 65532 + seccompProfile: + type: RuntimeDefault + imagePullSecrets: [] + restartPolicy: Never + serviceAccountName: gateway-helm-certgen + ttlSecondsAfterFinished: 30 +--- +# Source: gateway-helm/templates/envoy-proxy-topology-injector-webhook.yaml +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: 'envoy-gateway-topology-injector.envoy-gateway-system' + annotations: + "helm.sh/hook": pre-install, pre-upgrade + "helm.sh/hook-weight": "-1" + labels: + app.kubernetes.io/component: topology-injector + helm.sh/chart: gateway-helm-v0.0.0-latest + app.kubernetes.io/name: gateway-helm + app.kubernetes.io/instance: gateway-helm + app.kubernetes.io/version: "latest" + app.kubernetes.io/managed-by: Helm +webhooks: + - name: topology.webhook.gateway.envoyproxy.io + admissionReviewVersions: ["v1"] + sideEffects: None + clientConfig: + service: + name: envoy-gateway + namespace: 'envoy-gateway-system' + path: "/inject-pod-topology" + port: 9443 + failurePolicy: Ignore + rules: + - operations: ["CREATE"] + apiGroups: [""] + apiVersions: ["v1"] + resources: ["pods/binding"] + namespaceSelector: + matchExpressions: + - key: kubernetes.io/metadata.name + operator: In + values: + - envoy-gateway-system From 6e347dca1eb5a36f8ffb0abaa33711418732d240 Mon Sep 17 00:00:00 2001 From: zirain Date: Sun, 4 May 2025 11:09:22 +0800 Subject: [PATCH 08/66] chore: improve merge test (#5861) Signed-off-by: Arko Dasgupta --- internal/cmd/egctl/config_test.go | 5 +- internal/cmd/egctl/translate_test.go | 5 +- internal/gatewayapi/resource/load_test.go | 6 +- internal/gatewayapi/translator_test.go | 8 +- .../proxy/resource_provider_test.go | 8 +- .../ratelimit/resource_provider_test.go | 10 +- internal/metrics/metrics_test.go | 9 +- internal/utils/merge_test.go | 173 +++++------------- internal/utils/test/flags.go | 14 ++ .../backendtrafficpolicy_httpupgrade.in.yaml | 13 ++ ...afficpolicy_httpupgrade.jsonmerge.out.yaml | 18 ++ ...ackendtrafficpolicy_httpupgrade.patch.yaml | 12 ++ ...policy_httpupgrade.strategicmerge.out.yaml | 18 ++ internal/xds/bootstrap/bootstrap_test.go | 3 +- internal/xds/bootstrap/util_test.go | 6 +- .../xds/translator/listener_ready_test.go | 3 +- internal/xds/translator/translator_test.go | 16 +- tools/make/golang.mk | 1 + 18 files changed, 155 insertions(+), 173 deletions(-) create mode 100644 internal/utils/test/flags.go create mode 100644 internal/utils/testdata/backendtrafficpolicy_httpupgrade.in.yaml create mode 100644 internal/utils/testdata/backendtrafficpolicy_httpupgrade.jsonmerge.out.yaml create mode 100644 internal/utils/testdata/backendtrafficpolicy_httpupgrade.patch.yaml create mode 100644 internal/utils/testdata/backendtrafficpolicy_httpupgrade.strategicmerge.out.yaml diff --git a/internal/cmd/egctl/config_test.go b/internal/cmd/egctl/config_test.go index e7fb062f9d..7d54072538 100644 --- a/internal/cmd/egctl/config_test.go +++ b/internal/cmd/egctl/config_test.go @@ -29,6 +29,7 @@ import ( kube "github.com/envoyproxy/gateway/internal/kubernetes" "github.com/envoyproxy/gateway/internal/utils/file" netutil "github.com/envoyproxy/gateway/internal/utils/net" + "github.com/envoyproxy/gateway/internal/utils/test" ) const ( @@ -118,7 +119,7 @@ func TestExtractAllConfigDump(t *testing.T) { aggregated := sampleAggregatedConfigDump(configDump) got, err := marshalEnvoyProxyConfig(aggregated, tc.output) require.NoError(t, err) - if *overrideTestData { + if test.OverrideTestData() { require.NoError(t, file.Write(string(got), filepath.Join("testdata", "config", "out", tc.expected))) } out, err := readOutputConfig(tc.expected) @@ -206,7 +207,7 @@ func TestExtractSubResourcesConfigDump(t *testing.T) { aggregated := sampleAggregatedConfigDump(configDump) got, err := marshalEnvoyProxyConfig(aggregated, tc.output) require.NoError(t, err) - if *overrideTestData { + if test.OverrideTestData() { require.NoError(t, file.Write(string(got), filepath.Join("testdata", "config", "out", tc.expected))) } out, err := readOutputConfig(tc.expected) diff --git a/internal/cmd/egctl/translate_test.go b/internal/cmd/egctl/translate_test.go index 030ca31ac5..f7c401a9a0 100644 --- a/internal/cmd/egctl/translate_test.go +++ b/internal/cmd/egctl/translate_test.go @@ -25,10 +25,9 @@ import ( "github.com/envoyproxy/gateway/internal/gatewayapi/resource" "github.com/envoyproxy/gateway/internal/utils/field" "github.com/envoyproxy/gateway/internal/utils/file" + "github.com/envoyproxy/gateway/internal/utils/test" ) -var overrideTestData = flag.Bool("override-testdata", false, "if override the test output data.") - func TestTranslate(t *testing.T) { testCases := []struct { name string @@ -363,7 +362,7 @@ func TestTranslate(t *testing.T) { out, err = yaml.Marshal(got) require.NoError(t, err) } - if *overrideTestData { + if test.OverrideTestData() { require.NoError(t, file.Write(string(out), filepath.Join("testdata", "translate", "out", fn))) } want := &TranslationResult{} diff --git a/internal/gatewayapi/resource/load_test.go b/internal/gatewayapi/resource/load_test.go index fcfbfe1cf9..3fe0083661 100644 --- a/internal/gatewayapi/resource/load_test.go +++ b/internal/gatewayapi/resource/load_test.go @@ -6,7 +6,6 @@ package resource import ( - "flag" "fmt" "os" "path/filepath" @@ -18,10 +17,9 @@ import ( "sigs.k8s.io/yaml" "github.com/envoyproxy/gateway/internal/utils/file" + "github.com/envoyproxy/gateway/internal/utils/test" ) -var overrideTestData = flag.Bool("override-testdata", false, "if override the test output data.") - func TestIterYAMLBytes(t *testing.T) { inputs := `test: foo1 --- @@ -53,7 +51,7 @@ func TestLoadAllSupportedResourcesFromYAMLBytes(t *testing.T) { got, err := LoadResourcesFromYAMLBytes(inFile, true) require.NoError(t, err) - if *overrideTestData { + if test.OverrideTestData() { out, err := yaml.Marshal(got) require.NoError(t, err) require.NoError(t, file.Write(string(out), filepath.Join("testdata", "all-resources.out.yaml"))) diff --git a/internal/gatewayapi/translator_test.go b/internal/gatewayapi/translator_test.go index af8349df75..5193944295 100644 --- a/internal/gatewayapi/translator_test.go +++ b/internal/gatewayapi/translator_test.go @@ -10,7 +10,6 @@ import ( "context" "crypto/sha256" "encoding/hex" - "flag" "fmt" "os" "path/filepath" @@ -38,11 +37,10 @@ import ( "github.com/envoyproxy/gateway/internal/ir" "github.com/envoyproxy/gateway/internal/utils/field" "github.com/envoyproxy/gateway/internal/utils/file" + "github.com/envoyproxy/gateway/internal/utils/test" "github.com/envoyproxy/gateway/internal/wasm" ) -var overrideTestData = flag.Bool("override-testdata", false, "if override the test output data.") - func mustUnmarshal(t *testing.T, val []byte, out interface{}) { require.NoError(t, yaml.UnmarshalStrict(val, out, yaml.DisallowUnknownFields)) } @@ -326,7 +324,7 @@ func TestTranslate(t *testing.T) { out, err := yaml.Marshal(got) require.NoError(t, err) - if *overrideTestData { + if test.OverrideTestData() { overrideOutputConfig(t, string(out), outputFilePath) } @@ -527,7 +525,7 @@ func TestTranslateWithExtensionKinds(t *testing.T) { out, err := yaml.Marshal(got) require.NoError(t, err) - if *overrideTestData { + if test.OverrideTestData() { require.NoError(t, file.Write(string(out), outputFilePath)) } diff --git a/internal/infrastructure/kubernetes/proxy/resource_provider_test.go b/internal/infrastructure/kubernetes/proxy/resource_provider_test.go index ea622b987e..0651a433af 100644 --- a/internal/infrastructure/kubernetes/proxy/resource_provider_test.go +++ b/internal/infrastructure/kubernetes/proxy/resource_provider_test.go @@ -6,7 +6,6 @@ package proxy import ( - "flag" "fmt" "os" "sort" @@ -30,10 +29,9 @@ import ( "github.com/envoyproxy/gateway/internal/envoygateway/config" "github.com/envoyproxy/gateway/internal/gatewayapi" "github.com/envoyproxy/gateway/internal/ir" + "github.com/envoyproxy/gateway/internal/utils/test" ) -var overrideTestData = flag.Bool("override-testdata", false, "if override the test output data.") - const ( // envoyHTTPPort is the container port number of Envoy's HTTP endpoint. envoyHTTPPort = int32(8080) @@ -634,7 +632,7 @@ func TestDeployment(t *testing.T) { dp, err := r.Deployment() require.NoError(t, err) - if *overrideTestData { + if test.OverrideTestData() { deploymentYAML, err := yaml.Marshal(dp) require.NoError(t, err) // nolint: gosec @@ -1072,7 +1070,7 @@ func TestDaemonSet(t *testing.T) { }) } - if *overrideTestData { + if test.OverrideTestData() { deploymentYAML, err := yaml.Marshal(ds) require.NoError(t, err) // nolint: gosec diff --git a/internal/infrastructure/kubernetes/ratelimit/resource_provider_test.go b/internal/infrastructure/kubernetes/ratelimit/resource_provider_test.go index a7bc1479e5..f6b9b3a914 100644 --- a/internal/infrastructure/kubernetes/ratelimit/resource_provider_test.go +++ b/internal/infrastructure/kubernetes/ratelimit/resource_provider_test.go @@ -6,7 +6,6 @@ package ratelimit import ( - "flag" "fmt" "os" "strconv" @@ -27,10 +26,9 @@ import ( egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" "github.com/envoyproxy/gateway/internal/envoygateway/config" + "github.com/envoyproxy/gateway/internal/utils/test" ) -var overrideTestData = flag.Bool("override-testdata", false, "if override the test output data.") - const ( // RedisAuthEnvVar is the redis auth. RedisAuthEnvVar = "REDIS_AUTH" @@ -200,7 +198,7 @@ func TestConfigmap(t *testing.T) { cm, err := r.ConfigMap("") require.NoError(t, err) - if *overrideTestData { + if test.OverrideTestData() { cmYAML, err := yaml.Marshal(cm) require.NoError(t, err) // nolint:gosec @@ -770,7 +768,7 @@ func TestDeployment(t *testing.T) { dp, err := r.Deployment() require.NoError(t, err) - if *overrideTestData { + if test.OverrideTestData() { deploymentYAML, err := yaml.Marshal(dp) require.NoError(t, err) // nolint:gosec @@ -886,7 +884,7 @@ func TestHorizontalPodAutoscaler(t *testing.T) { hpa, err := r.HorizontalPodAutoscaler() require.NoError(t, err) - if *overrideTestData { + if test.OverrideTestData() { hpaYAML, err := yaml.Marshal(hpa) require.NoError(t, err) // nolint:gosec diff --git a/internal/metrics/metrics_test.go b/internal/metrics/metrics_test.go index 55b682aff7..fcd05c37d4 100644 --- a/internal/metrics/metrics_test.go +++ b/internal/metrics/metrics_test.go @@ -10,7 +10,6 @@ import ( "context" "encoding/json" "errors" - "flag" "fmt" "io" "os" @@ -28,9 +27,9 @@ import ( "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.17.0" -) -var overrideTestData = flag.Bool("override-testdata", false, "if override the test output data.") + "github.com/envoyproxy/gateway/internal/utils/test" +) func TestCounter(t *testing.T) { name := "counter_metric" @@ -213,7 +212,7 @@ func newTestMetricsProvider(metricType string, writer io.Writer) (*metric.MeterP } func loadMetricsFile(t *testing.T, name string, reader io.Reader) { - if !*overrideTestData { + if !test.OverrideTestData() { fname := fmt.Sprintf("testdata/%s.json", name) // nolint:gosec @@ -229,7 +228,7 @@ func loadMetricsFile(t *testing.T, name string, reader io.Reader) { } func exporterWriter(name string, origin io.ReadWriter) (io.ReadWriter, error) { - if *overrideTestData { + if test.OverrideTestData() { fname := fmt.Sprintf("testdata/%s.json", name) // nolint:gosec diff --git a/internal/utils/merge_test.go b/internal/utils/merge_test.go index 690a331f9a..73f6a584c9 100644 --- a/internal/utils/merge_test.go +++ b/internal/utils/merge_test.go @@ -6,146 +6,63 @@ package utils import ( - "fmt" + "os" + "path/filepath" + "strings" "testing" "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/envoyproxy/gateway/internal/utils/test" ) func TestMergeBackendTrafficPolicy(t *testing.T) { - r := resource.MustParse("100m") + baseDir := "testdata" + caseFiles, err := filepath.Glob(filepath.Join(baseDir, "backendtrafficpolicy_*.in.yaml")) + require.NoError(t, err) - cases := []struct { - name string - original *egv1a1.BackendTrafficPolicy - patch *egv1a1.BackendTrafficPolicy + for _, caseFile := range caseFiles { + // get case name from path + caseName := strings.TrimPrefix(strings.TrimSuffix(caseFile, ".in.yaml"), baseDir+"/backendtrafficpolicy_") + t.Run(caseName, func(t *testing.T) { + for _, mergeType := range []egv1a1.MergeType{egv1a1.StrategicMerge, egv1a1.JSONMerge} { + patchedInput := strings.Replace(caseFile, ".in.yaml", ".patch.yaml", 1) + var output string + if mergeType == egv1a1.StrategicMerge { + output = strings.Replace(caseFile, ".in.yaml", ".strategicmerge.out.yaml", 1) + } else { + output = strings.Replace(caseFile, ".in.yaml", ".jsonmerge.out.yaml", 1) + } - expected *egv1a1.BackendTrafficPolicy - jsonMergeExpected *egv1a1.BackendTrafficPolicy - }{ - { - name: "merge", - original: &egv1a1.BackendTrafficPolicy{ - Spec: egv1a1.BackendTrafficPolicySpec{ - ClusterSettings: egv1a1.ClusterSettings{ - Connection: &egv1a1.BackendConnection{ - BufferLimit: &r, - }, - Retry: &egv1a1.Retry{ - NumRetries: ptr.To[int32](2), - }, - }, - HTTPUpgrade: []*egv1a1.ProtocolUpgradeConfig{ - { - Type: "original", - }, - }, - }, - }, - patch: &egv1a1.BackendTrafficPolicy{ - Spec: egv1a1.BackendTrafficPolicySpec{ - ClusterSettings: egv1a1.ClusterSettings{ - Retry: &egv1a1.Retry{ - NumRetries: ptr.To[int32](3), - }, - }, - HTTPUpgrade: []*egv1a1.ProtocolUpgradeConfig{ - { - Type: "patched", - }, - }, - }, - }, - expected: &egv1a1.BackendTrafficPolicy{ - Spec: egv1a1.BackendTrafficPolicySpec{ - ClusterSettings: egv1a1.ClusterSettings{ - Connection: &egv1a1.BackendConnection{ - BufferLimit: &r, - }, - Retry: &egv1a1.Retry{ - NumRetries: ptr.To[int32](3), - }, - }, - HTTPUpgrade: []*egv1a1.ProtocolUpgradeConfig{ - { - Type: "patched", - }, - { - Type: "original", - }, - }, - }, - }, - jsonMergeExpected: &egv1a1.BackendTrafficPolicy{ - Spec: egv1a1.BackendTrafficPolicySpec{ - ClusterSettings: egv1a1.ClusterSettings{ - Connection: &egv1a1.BackendConnection{ - BufferLimit: &r, - }, - Retry: &egv1a1.Retry{ - NumRetries: ptr.To[int32](3), - }, - }, - HTTPUpgrade: []*egv1a1.ProtocolUpgradeConfig{ - { - Type: "patched", - }, - }, - }, - }, - }, - { - name: "override", - original: &egv1a1.BackendTrafficPolicy{ - Spec: egv1a1.BackendTrafficPolicySpec{ - ClusterSettings: egv1a1.ClusterSettings{ - Retry: &egv1a1.Retry{ - NumRetries: ptr.To[int32](13), - }, - }, - }, - }, - patch: &egv1a1.BackendTrafficPolicy{ - Spec: egv1a1.BackendTrafficPolicySpec{ - ClusterSettings: egv1a1.ClusterSettings{ - Retry: &egv1a1.Retry{ - NumRetries: ptr.To[int32](3), - }, - }, - }, - }, - expected: &egv1a1.BackendTrafficPolicy{ - Spec: egv1a1.BackendTrafficPolicySpec{ - ClusterSettings: egv1a1.ClusterSettings{ - Retry: &egv1a1.Retry{ - NumRetries: ptr.To[int32](3), - }, - }, - }, - }, - }, - } - for _, tc := range cases { - for _, mergeType := range []egv1a1.MergeType{egv1a1.StrategicMerge, egv1a1.JSONMerge} { - t.Run(fmt.Sprintf("%s/%s", mergeType, tc.name), func(t *testing.T) { - got, err := Merge[*egv1a1.BackendTrafficPolicy](tc.original, tc.patch, mergeType) + original := readObject[*egv1a1.BackendTrafficPolicy](t, caseFile) + patch := readObject[*egv1a1.BackendTrafficPolicy](t, patchedInput) + + got, err := Merge(original, patch, mergeType) require.NoError(t, err) - switch mergeType { - case egv1a1.StrategicMerge: - require.Equal(t, tc.expected, got) - case egv1a1.JSONMerge: - if tc.jsonMergeExpected != nil { - require.Equal(t, tc.jsonMergeExpected, got) - } else { - require.Equal(t, tc.expected, got) - } + if test.OverrideTestData() { + b, err := yaml.Marshal(got) + require.NoError(t, err) + require.NoError(t, os.WriteFile(output, b, 0o600)) + continue } - }) - } + + expected := readObject[*egv1a1.BackendTrafficPolicy](t, output) + require.Equal(t, expected, got) + } + }) } } + +func readObject[T client.Object](t *testing.T, path string) T { + t.Helper() + b, err := os.ReadFile(path) + require.NoError(t, err) + btp := new(T) + err = yaml.Unmarshal(b, btp) + require.NoError(t, err) + return *btp +} diff --git a/internal/utils/test/flags.go b/internal/utils/test/flags.go new file mode 100644 index 0000000000..95e261b253 --- /dev/null +++ b/internal/utils/test/flags.go @@ -0,0 +1,14 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package test + +import "flag" + +var overrideTestData = flag.Bool("override-testdata", false, "if override the test output data.") + +func OverrideTestData() bool { + return *overrideTestData +} diff --git a/internal/utils/testdata/backendtrafficpolicy_httpupgrade.in.yaml b/internal/utils/testdata/backendtrafficpolicy_httpupgrade.in.yaml new file mode 100644 index 0000000000..20af7411b0 --- /dev/null +++ b/internal/utils/testdata/backendtrafficpolicy_httpupgrade.in.yaml @@ -0,0 +1,13 @@ +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: original +spec: + timeout: + tcp: + connectTimeout: 15s + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + httpUpgrade: + - type: websocket diff --git a/internal/utils/testdata/backendtrafficpolicy_httpupgrade.jsonmerge.out.yaml b/internal/utils/testdata/backendtrafficpolicy_httpupgrade.jsonmerge.out.yaml new file mode 100644 index 0000000000..b9f7db73e2 --- /dev/null +++ b/internal/utils/testdata/backendtrafficpolicy_httpupgrade.jsonmerge.out.yaml @@ -0,0 +1,18 @@ +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + creationTimestamp: null + name: patched +spec: + connection: + bufferLimit: 100M + httpUpgrade: + - type: websocket + timeout: + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + tcp: + connectTimeout: 15s +status: + ancestors: null diff --git a/internal/utils/testdata/backendtrafficpolicy_httpupgrade.patch.yaml b/internal/utils/testdata/backendtrafficpolicy_httpupgrade.patch.yaml new file mode 100644 index 0000000000..364d73ce0f --- /dev/null +++ b/internal/utils/testdata/backendtrafficpolicy_httpupgrade.patch.yaml @@ -0,0 +1,12 @@ +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: patched +spec: + timeout: + tcp: + connectTimeout: 15s + connection: + bufferLimit: 100M + httpUpgrade: + - type: "websocket" diff --git a/internal/utils/testdata/backendtrafficpolicy_httpupgrade.strategicmerge.out.yaml b/internal/utils/testdata/backendtrafficpolicy_httpupgrade.strategicmerge.out.yaml new file mode 100644 index 0000000000..b9f7db73e2 --- /dev/null +++ b/internal/utils/testdata/backendtrafficpolicy_httpupgrade.strategicmerge.out.yaml @@ -0,0 +1,18 @@ +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + creationTimestamp: null + name: patched +spec: + connection: + bufferLimit: 100M + httpUpgrade: + - type: websocket + timeout: + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + tcp: + connectTimeout: 15s +status: + ancestors: null diff --git a/internal/xds/bootstrap/bootstrap_test.go b/internal/xds/bootstrap/bootstrap_test.go index 70216bb41f..4ef244fd55 100644 --- a/internal/xds/bootstrap/bootstrap_test.go +++ b/internal/xds/bootstrap/bootstrap_test.go @@ -17,6 +17,7 @@ import ( gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/envoyproxy/gateway/internal/utils/test" ) func TestGetRenderedBootstrapConfig(t *testing.T) { @@ -187,7 +188,7 @@ func TestGetRenderedBootstrapConfig(t *testing.T) { got, err := GetRenderedBootstrapConfig(tc.opts) require.NoError(t, err) - if *overrideTestData { + if test.OverrideTestData() { // nolint:gosec err = os.WriteFile(path.Join("testdata", "render", fmt.Sprintf("%s.yaml", tc.name)), []byte(got), 0o644) require.NoError(t, err) diff --git a/internal/xds/bootstrap/util_test.go b/internal/xds/bootstrap/util_test.go index b5cacb7afd..d105456a56 100644 --- a/internal/xds/bootstrap/util_test.go +++ b/internal/xds/bootstrap/util_test.go @@ -6,7 +6,6 @@ package bootstrap import ( - "flag" "fmt" "os" "path" @@ -17,10 +16,9 @@ import ( "sigs.k8s.io/yaml" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/envoyproxy/gateway/internal/utils/test" ) -var overrideTestData = flag.Bool("override-testdata", false, "if override the test output data.") - func TestApplyBootstrapConfig(t *testing.T) { str, _ := readTestData("enable-prometheus") cases := []struct { @@ -74,7 +72,7 @@ func TestApplyBootstrapConfig(t *testing.T) { data, err := ApplyBootstrapConfig(tc.boostrapConfig, tc.defaultBootstrap) require.NoError(t, err) - if *overrideTestData { + if test.OverrideTestData() { // nolint:gosec err = os.WriteFile(path.Join("testdata", "merge", fmt.Sprintf("%s.out.yaml", tc.name)), []byte(data), 0o644) require.NoError(t, err) diff --git a/internal/xds/translator/listener_ready_test.go b/internal/xds/translator/listener_ready_test.go index ec522ad7bb..70935476e9 100644 --- a/internal/xds/translator/listener_ready_test.go +++ b/internal/xds/translator/listener_ready_test.go @@ -18,6 +18,7 @@ import ( egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" "github.com/envoyproxy/gateway/internal/ir" "github.com/envoyproxy/gateway/internal/utils/proto" + "github.com/envoyproxy/gateway/internal/utils/test" ) func TestBuildReadyListener(t *testing.T) { @@ -61,7 +62,7 @@ func TestBuildReadyListener(t *testing.T) { t.Errorf("unexpected error: %v", err) } - if *overrideTestData { + if test.OverrideTestData() { data, err := proto.ToYAML(got) require.NoError(t, err) err = os.WriteFile(path.Join("testdata", "readylistener", tc.name+".yaml"), data, 0o600) diff --git a/internal/xds/translator/translator_test.go b/internal/xds/translator/translator_test.go index 3dd4edae9d..5aefb9b37f 100644 --- a/internal/xds/translator/translator_test.go +++ b/internal/xds/translator/translator_test.go @@ -7,7 +7,6 @@ package translator import ( "embed" - "flag" "path/filepath" "runtime" "sort" @@ -30,6 +29,7 @@ import ( "github.com/envoyproxy/gateway/internal/ir" "github.com/envoyproxy/gateway/internal/utils/field" "github.com/envoyproxy/gateway/internal/utils/file" + "github.com/envoyproxy/gateway/internal/utils/test" xtypes "github.com/envoyproxy/gateway/internal/xds/types" "github.com/envoyproxy/gateway/internal/xds/utils" ) @@ -39,8 +39,6 @@ var ( outFiles embed.FS //go:embed testdata/in/* inFiles embed.FS - - overrideTestData = flag.Bool("override-testdata", false, "if override the test output data.") ) type testFileConfig struct { @@ -178,7 +176,7 @@ func TestTranslateXds(t *testing.T) { routes := tCtx.XdsResources[resourcev3.RouteType] clusters := tCtx.XdsResources[resourcev3.ClusterType] endpoints := tCtx.XdsResources[resourcev3.EndpointType] - if *overrideTestData { + if test.OverrideTestData() { require.NoError(t, file.Write(requireResourcesToYAMLString(t, listeners), filepath.Join("testdata", "out", "xds-ir", inputFileName+".listeners.yaml"))) require.NoError(t, file.Write(requireResourcesToYAMLString(t, routes), filepath.Join("testdata", "out", "xds-ir", inputFileName+".routes.yaml"))) require.NoError(t, file.Write(requireResourcesToYAMLString(t, clusters), filepath.Join("testdata", "out", "xds-ir", inputFileName+".clusters.yaml"))) @@ -191,7 +189,7 @@ func TestTranslateXds(t *testing.T) { secrets, ok := tCtx.XdsResources[resourcev3.SecretType] if ok && len(secrets) > 0 { - if *overrideTestData { + if test.OverrideTestData() { require.NoError(t, file.Write(requireResourcesToYAMLString(t, secrets), filepath.Join("testdata", "out", "xds-ir", inputFileName+".secrets.yaml"))) } require.Equal(t, requireTestDataOutFile(t, "xds-ir", inputFileName+".secrets.yaml"), requireResourcesToYAMLString(t, secrets)) @@ -202,7 +200,7 @@ func TestTranslateXds(t *testing.T) { for _, e := range got { require.NoError(t, field.SetValue(e, "LastTransitionTime", metav1.NewTime(time.Time{}))) } - if *overrideTestData { + if test.OverrideTestData() { out, err := yaml.Marshal(got) require.NoError(t, err) require.NoError(t, file.Write(string(out), filepath.Join("testdata", "out", "xds-ir", inputFileName+".envoypatchpolicies.yaml"))) @@ -231,7 +229,7 @@ func TestTranslateRateLimitConfig(t *testing.T) { // Call BuildRateLimitServiceConfig with the list of listeners configs := BuildRateLimitServiceConfig(listeners) - if *overrideTestData { + if test.OverrideTestData() { require.NoError(t, file.Write(requireRateLimitConfigsToYAMLString(t, configs), filepath.Join("testdata", "out", "ratelimit-config", inputFileName+".yaml"))) } require.Equal(t, requireTestDataOutFile(t, "ratelimit-config", inputFileName+".yaml"), requireRateLimitConfigsToYAMLString(t, configs)) @@ -317,7 +315,7 @@ func TestTranslateXdsWithExtensionErrorsWhenFailOpen(t *testing.T) { routes := tCtx.XdsResources[resourcev3.RouteType] clusters := tCtx.XdsResources[resourcev3.ClusterType] endpoints := tCtx.XdsResources[resourcev3.EndpointType] - if *overrideTestData { + if test.OverrideTestData() { require.NoError(t, file.Write(requireResourcesToYAMLString(t, listeners), filepath.Join("testdata", "out", "extension-xds-ir", inputFileName+".listeners.yaml"))) require.NoError(t, file.Write(requireResourcesToYAMLString(t, routes), filepath.Join("testdata", "out", "extension-xds-ir", inputFileName+".routes.yaml"))) require.NoError(t, file.Write(requireResourcesToYAMLString(t, clusters), filepath.Join("testdata", "out", "extension-xds-ir", inputFileName+".clusters.yaml"))) @@ -330,7 +328,7 @@ func TestTranslateXdsWithExtensionErrorsWhenFailOpen(t *testing.T) { secrets, ok := tCtx.XdsResources[resourcev3.SecretType] if ok { - if *overrideTestData { + if test.OverrideTestData() { require.NoError(t, file.Write(requireResourcesToYAMLString(t, secrets), filepath.Join("testdata", "out", "extension-xds-ir", inputFileName+".secrets.yaml"))) } require.Equal(t, requireTestDataOutFile(t, "extension-xds-ir", inputFileName+".secrets.yaml"), requireResourcesToYAMLString(t, secrets)) diff --git a/tools/make/golang.mk b/tools/make/golang.mk index 2518df6605..50a1825275 100644 --- a/tools/make/golang.mk +++ b/tools/make/golang.mk @@ -56,6 +56,7 @@ go.test.unit: ## Run go unit tests .PHONY: go.testdata.complete go.testdata.complete: ## Override test ouputdata @$(LOG_TARGET) + go test -timeout 30s github.com/envoyproxy/gateway/internal/utils --override-testdata=true go test -timeout 30s github.com/envoyproxy/gateway/internal/xds/translator --override-testdata=true go test -timeout 30s github.com/envoyproxy/gateway/internal/cmd/egctl --override-testdata=true go test -timeout 30s github.com/envoyproxy/gateway/internal/infrastructure/kubernetes/ratelimit --override-testdata=true From 6d3ab1a6a6e7076107ebf18c642937ac8b18206f Mon Sep 17 00:00:00 2001 From: Kota Kimura <86363983+kkk777-7@users.noreply.github.com> Date: Sun, 4 May 2025 23:44:48 +0900 Subject: [PATCH 09/66] fix: httproute precedence by considering header/query match type (#5740) * fix precedence to use number of exact matches Signed-off-by: kkk777-7 Signed-off-by: Arko Dasgupta --- internal/gatewayapi/sort.go | 32 ++- ...oute-with-header-match-diff-number.in.yaml | 68 ++++++ ...ute-with-header-match-diff-number.out.yaml | 228 ++++++++++++++++++ ...proute-with-header-match-diff-type.in.yaml | 65 +++++ ...route-with-header-match-diff-type.out.yaml | 222 +++++++++++++++++ ...route-with-query-match-diff-number.in.yaml | 68 ++++++ ...oute-with-query-match-diff-number.out.yaml | 228 ++++++++++++++++++ ...tproute-with-query-match-diff-type.in.yaml | 65 +++++ ...proute-with-query-match-diff-type.out.yaml | 222 +++++++++++++++++ 9 files changed, 1197 insertions(+), 1 deletion(-) create mode 100644 internal/gatewayapi/testdata/httproute-with-header-match-diff-number.in.yaml create mode 100644 internal/gatewayapi/testdata/httproute-with-header-match-diff-number.out.yaml create mode 100644 internal/gatewayapi/testdata/httproute-with-header-match-diff-type.in.yaml create mode 100644 internal/gatewayapi/testdata/httproute-with-header-match-diff-type.out.yaml create mode 100644 internal/gatewayapi/testdata/httproute-with-query-match-diff-number.in.yaml create mode 100644 internal/gatewayapi/testdata/httproute-with-query-match-diff-number.out.yaml create mode 100644 internal/gatewayapi/testdata/httproute-with-query-match-diff-type.in.yaml create mode 100644 internal/gatewayapi/testdata/httproute-with-query-match-diff-type.out.yaml diff --git a/internal/gatewayapi/sort.go b/internal/gatewayapi/sort.go index e8042c85e3..bd859d6c0b 100644 --- a/internal/gatewayapi/sort.go +++ b/internal/gatewayapi/sort.go @@ -63,6 +63,7 @@ func (x XdsIRRoutes) Less(i, j int) bool { // Equal case // 3. Sort based on the number of Header matches. + // When the number is same, sort based on number of Exact Header matches. hCountI := len(x[i].HeaderMatches) hCountJ := len(x[j].HeaderMatches) if hCountI < hCountJ { @@ -71,12 +72,31 @@ func (x XdsIRRoutes) Less(i, j int) bool { if hCountI > hCountJ { return false } + + hExtNumberI := numberOfExactMatches(x[i].HeaderMatches) + hExtNumberJ := numberOfExactMatches(x[j].HeaderMatches) + if hExtNumberI < hExtNumberJ { + return true + } + if hExtNumberI > hExtNumberJ { + return false + } // Equal case // 4. Sort based on the number of Query param matches. + // When the number is same, sort based on number of Exact Query param matches. qCountI := len(x[i].QueryParamMatches) qCountJ := len(x[j].QueryParamMatches) - return qCountI < qCountJ + if qCountI < qCountJ { + return true + } + if qCountI > qCountJ { + return false + } + + qExtNumberI := numberOfExactMatches(x[i].QueryParamMatches) + qExtNumberJ := numberOfExactMatches(x[j].QueryParamMatches) + return qExtNumberI < qExtNumberJ } // sortXdsIR sorts the xdsIR based on the match precedence @@ -107,3 +127,13 @@ func pathMatchCount(pathMatch *ir.StringMatch) int { } return 0 } + +func numberOfExactMatches(stringMatches []*ir.StringMatch) int { + var cnt int + for _, stringMatch := range stringMatches { + if stringMatch != nil && stringMatch.Exact != nil { + cnt++ + } + } + return cnt +} diff --git a/internal/gatewayapi/testdata/httproute-with-header-match-diff-number.in.yaml b/internal/gatewayapi/testdata/httproute-with-header-match-diff-number.in.yaml new file mode 100644 index 0000000000..7223aef583 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-header-match-diff-number.in.yaml @@ -0,0 +1,68 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + hostname: '*.envoyproxy.io' + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: '/' + headers: + - type: RegularExpression + name: x-org-id + value: '.*' + - type: RegularExpression + name: hostname + value: '.*' + backendRefs: + - name: service-1 + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: '/' + headers: + - type: Exact + name: x-org-id + value: test-org + backendRefs: + - name: service-1 + port: 8080 diff --git a/internal/gatewayapi/testdata/httproute-with-header-match-diff-number.out.yaml b/internal/gatewayapi/testdata/httproute-with-header-match-diff-number.out.yaml new file mode 100644 index 0000000000..8952792385 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-header-match-diff-number.out.yaml @@ -0,0 +1,228 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + hostname: '*.envoyproxy.io' + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 2 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - headers: + - name: x-org-id + type: RegularExpression + value: .* + - name: hostname + type: RegularExpression + value: .* + path: + type: PathPrefix + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-2 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - headers: + - name: x-org-id + type: Exact + value: test-org + path: + type: PathPrefix + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*.envoyproxy.io' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + headerMatches: + - distinct: false + name: x-org-id + safeRegex: .* + - distinct: false + name: hostname + safeRegex: .* + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + - destination: + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-2/rule/0/backend/0 + protocol: HTTP + weight: 1 + headerMatches: + - distinct: false + exact: test-org + name: x-org-id + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-2 + namespace: default + name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/httproute-with-header-match-diff-type.in.yaml b/internal/gatewayapi/testdata/httproute-with-header-match-diff-type.in.yaml new file mode 100644 index 0000000000..06ae961aee --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-header-match-diff-type.in.yaml @@ -0,0 +1,65 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + hostname: '*.envoyproxy.io' + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: '/' + headers: + - type: RegularExpression + name: x-org-id + value: '.*' + backendRefs: + - name: service-1 + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: '/' + headers: + - type: Exact + name: x-org-id + value: test-org + backendRefs: + - name: service-1 + port: 8080 diff --git a/internal/gatewayapi/testdata/httproute-with-header-match-diff-type.out.yaml b/internal/gatewayapi/testdata/httproute-with-header-match-diff-type.out.yaml new file mode 100644 index 0000000000..454c35bc9a --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-header-match-diff-type.out.yaml @@ -0,0 +1,222 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + hostname: '*.envoyproxy.io' + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 2 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - headers: + - name: x-org-id + type: RegularExpression + value: .* + path: + type: PathPrefix + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-2 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - headers: + - name: x-org-id + type: Exact + value: test-org + path: + type: PathPrefix + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*.envoyproxy.io' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-2/rule/0/backend/0 + protocol: HTTP + weight: 1 + headerMatches: + - distinct: false + exact: test-org + name: x-org-id + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-2 + namespace: default + name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + headerMatches: + - distinct: false + name: x-org-id + safeRegex: .* + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/httproute-with-query-match-diff-number.in.yaml b/internal/gatewayapi/testdata/httproute-with-query-match-diff-number.in.yaml new file mode 100644 index 0000000000..adc40a7ed7 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-query-match-diff-number.in.yaml @@ -0,0 +1,68 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + hostname: '*.envoyproxy.io' + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: '/' + queryParams: + - type: RegularExpression + name: id + value: '.*' + - type: RegularExpression + name: name + value: '.*' + backendRefs: + - name: service-1 + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: '/' + queryParams: + - type: Exact + name: id + value: 1234 + backendRefs: + - name: service-1 + port: 8080 diff --git a/internal/gatewayapi/testdata/httproute-with-query-match-diff-number.out.yaml b/internal/gatewayapi/testdata/httproute-with-query-match-diff-number.out.yaml new file mode 100644 index 0000000000..2929ac417a --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-query-match-diff-number.out.yaml @@ -0,0 +1,228 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + hostname: '*.envoyproxy.io' + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 2 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + type: PathPrefix + value: / + queryParams: + - name: id + type: RegularExpression + value: .* + - name: name + type: RegularExpression + value: .* + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-2 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + type: PathPrefix + value: / + queryParams: + - name: id + type: Exact + value: "1234" + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*.envoyproxy.io' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + queryParamMatches: + - distinct: false + name: id + safeRegex: .* + - distinct: false + name: name + safeRegex: .* + - destination: + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-2/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-2 + namespace: default + name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + queryParamMatches: + - distinct: false + exact: "1234" + name: id + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/httproute-with-query-match-diff-type.in.yaml b/internal/gatewayapi/testdata/httproute-with-query-match-diff-type.in.yaml new file mode 100644 index 0000000000..128cefe8cd --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-query-match-diff-type.in.yaml @@ -0,0 +1,65 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + hostname: '*.envoyproxy.io' + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: '/' + queryParams: + - type: RegularExpression + name: id + value: '.*' + backendRefs: + - name: service-1 + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: '/' + queryParams: + - type: Exact + name: id + value: 1234 + backendRefs: + - name: service-1 + port: 8080 diff --git a/internal/gatewayapi/testdata/httproute-with-query-match-diff-type.out.yaml b/internal/gatewayapi/testdata/httproute-with-query-match-diff-type.out.yaml new file mode 100644 index 0000000000..b1dee44820 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-query-match-diff-type.out.yaml @@ -0,0 +1,222 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + hostname: '*.envoyproxy.io' + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 2 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + type: PathPrefix + value: / + queryParams: + - name: id + type: RegularExpression + value: .* + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-2 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + type: PathPrefix + value: / + queryParams: + - name: id + type: Exact + value: "1234" + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*.envoyproxy.io' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-2/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-2 + namespace: default + name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + queryParamMatches: + - distinct: false + exact: "1234" + name: id + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + queryParamMatches: + - distinct: false + name: id + safeRegex: .* + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 From 32b8d8045506f93cbfe959d5a093a67975bcbc93 Mon Sep 17 00:00:00 2001 From: zirain Date: Mon, 5 May 2025 11:13:45 +0800 Subject: [PATCH 10/66] ci: make helm-generate should failed as expected (#5908) Signed-off-by: zirain Signed-off-by: Arko Dasgupta --- tools/make/helm.mk | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/make/helm.mk b/tools/make/helm.mk index 2dc788de67..21797c33ba 100644 --- a/tools/make/helm.mk +++ b/tools/make/helm.mk @@ -73,11 +73,11 @@ helm-generate.%: @for file in $(wildcard test/helm/${CHART_NAME}/*.in.yaml); do \ filename=$$(basename $${file}); \ output="$${filename%.in.*}.out.yaml"; \ - if [ ${CHART_NAME} == "gateway-addons-helm" ]; then \ - helm template ${CHART_NAME} charts/${CHART_NAME} -f $${file} > test/helm/${CHART_NAME}/$$output --namespace=monitoring; \ - elif [ ${CHART_NAME} == "gateway-crds-helm" ]; then \ - helm template ${CHART_NAME} charts/${CHART_NAME} -f $${file} > test/helm/${CHART_NAME}/$$output; \ - else \ - helm template ${CHART_NAME} charts/${CHART_NAME} -f $${file} > test/helm/${CHART_NAME}/$$output --namespace=envoy-gateway-system; \ + if [ ${CHART_NAME} == "gateway-addons-helm" ]; then \ + helm template ${CHART_NAME} charts/${CHART_NAME} -f $${file} > test/helm/${CHART_NAME}/$$output --namespace=monitoring || exit 1; \ + elif [ ${CHART_NAME} == "gateway-crds-helm" ]; then \ + helm template ${CHART_NAME} charts/${CHART_NAME} -f $${file} > test/helm/${CHART_NAME}/$$output || exit 1; \ + else \ + helm template ${CHART_NAME} charts/${CHART_NAME} -f $${file} > test/helm/${CHART_NAME}/$$output --namespace=envoy-gateway-system || exit 1; \ fi; \ done From 24edeed223e0ea5850080412dfaa6113d377efc7 Mon Sep 17 00:00:00 2001 From: tomas-rojo <74457691+tomas-rojo@users.noreply.github.com> Date: Mon, 5 May 2025 05:45:46 +0200 Subject: [PATCH 11/66] docs(rate-limit): minor fix in 'Distinct Users Except Admin' section (#5912) Signed-off-by: Tomas Rojo Signed-off-by: Arko Dasgupta --- site/content/en/latest/tasks/traffic/global-rate-limit.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/site/content/en/latest/tasks/traffic/global-rate-limit.md b/site/content/en/latest/tasks/traffic/global-rate-limit.md index 47eac33bc3..63f940b150 100644 --- a/site/content/en/latest/tasks/traffic/global-rate-limit.md +++ b/site/content/en/latest/tasks/traffic/global-rate-limit.md @@ -493,6 +493,9 @@ spec: - headers: - type: Distinct name: x-user-id + - name: x-user-id + value: admin + invert: true limit: requests: 3 unit: Hour From f4f42e505099f06adcfdfa9e87ed8d13b98d58e0 Mon Sep 17 00:00:00 2001 From: zirain Date: Mon, 5 May 2025 12:27:38 +0800 Subject: [PATCH 12/66] adpot internals/utils/merge.Merge (#5917) Signed-off-by: Arko Dasgupta --- api/v1alpha1/kubernetes_helpers.go | 145 ------------------ .../kubernetes/proxy/resource_provider.go | 15 +- .../kubernetes/ratelimit/resource_provider.go | 6 +- internal/utils/merge.go | 38 ++++- 4 files changed, 42 insertions(+), 162 deletions(-) diff --git a/api/v1alpha1/kubernetes_helpers.go b/api/v1alpha1/kubernetes_helpers.go index 6dd6b5fbfc..e944df1c22 100644 --- a/api/v1alpha1/kubernetes_helpers.go +++ b/api/v1alpha1/kubernetes_helpers.go @@ -11,7 +11,6 @@ import ( jsonpatch "github.com/evanphx/json-patch" appsv1 "k8s.io/api/apps/v1" - autoscalingv2 "k8s.io/api/autoscaling/v2" corev1 "k8s.io/api/core/v1" policyv1 "k8s.io/api/policy/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -158,150 +157,6 @@ func (hpa *KubernetesHorizontalPodAutoscalerSpec) setDefault() { } } -// ApplyMergePatch applies a merge patch to a deployment based on the merge type -func (deployment *KubernetesDeploymentSpec) ApplyMergePatch(old *appsv1.Deployment) (*appsv1.Deployment, error) { - if deployment.Patch == nil { - return old, nil - } - - var patchedJSON []byte - var err error - - // Serialize the current deployment to JSON - originalJSON, err := json.Marshal(old) - if err != nil { - return nil, fmt.Errorf("error marshaling original deployment: %w", err) - } - - switch { - case deployment.Patch.Type == nil || *deployment.Patch.Type == StrategicMerge: - patchedJSON, err = strategicpatch.StrategicMergePatch(originalJSON, deployment.Patch.Value.Raw, appsv1.Deployment{}) - case *deployment.Patch.Type == JSONMerge: - patchedJSON, err = jsonpatch.MergePatch(originalJSON, deployment.Patch.Value.Raw) - default: - return nil, fmt.Errorf("unsupported merge type: %s", *deployment.Patch.Type) - } - if err != nil { - return nil, fmt.Errorf("error applying merge patch: %w", err) - } - - // Deserialize the patched JSON into a new deployment object - var patchedDeployment appsv1.Deployment - if err := json.Unmarshal(patchedJSON, &patchedDeployment); err != nil { - return nil, fmt.Errorf("error unmarshaling patched deployment: %w", err) - } - - return &patchedDeployment, nil -} - -// ApplyMergePatch applies a merge patch to a daemonset based on the merge type -func (daemonset *KubernetesDaemonSetSpec) ApplyMergePatch(old *appsv1.DaemonSet) (*appsv1.DaemonSet, error) { - if daemonset.Patch == nil { - return old, nil - } - - var patchedJSON []byte - var err error - - // Serialize the current daemonset to JSON - originalJSON, err := json.Marshal(old) - if err != nil { - return nil, fmt.Errorf("error marshaling original daemonset: %w", err) - } - - switch { - case daemonset.Patch.Type == nil || *daemonset.Patch.Type == StrategicMerge: - patchedJSON, err = strategicpatch.StrategicMergePatch(originalJSON, daemonset.Patch.Value.Raw, appsv1.DaemonSet{}) - case *daemonset.Patch.Type == JSONMerge: - patchedJSON, err = jsonpatch.MergePatch(originalJSON, daemonset.Patch.Value.Raw) - default: - return nil, fmt.Errorf("unsupported merge type: %s", *daemonset.Patch.Type) - } - if err != nil { - return nil, fmt.Errorf("error applying merge patch: %w", err) - } - - // Deserialize the patched JSON into a new daemonset object - var patchedDaemonSet appsv1.DaemonSet - if err := json.Unmarshal(patchedJSON, &patchedDaemonSet); err != nil { - return nil, fmt.Errorf("error unmarshaling patched daemonset: %w", err) - } - - return &patchedDaemonSet, nil -} - -// ApplyMergePatch applies a merge patch to a service based on the merge type -func (service *KubernetesServiceSpec) ApplyMergePatch(old *corev1.Service) (*corev1.Service, error) { - if service.Patch == nil { - return old, nil - } - - var patchedJSON []byte - var err error - - // Serialize the current service to JSON - originalJSON, err := json.Marshal(old) - if err != nil { - return nil, fmt.Errorf("error marshaling original service: %w", err) - } - - switch { - case service.Patch.Type == nil || *service.Patch.Type == StrategicMerge: - patchedJSON, err = strategicpatch.StrategicMergePatch(originalJSON, service.Patch.Value.Raw, corev1.Service{}) - case *service.Patch.Type == JSONMerge: - patchedJSON, err = jsonpatch.MergePatch(originalJSON, service.Patch.Value.Raw) - default: - return nil, fmt.Errorf("unsupported merge type: %s", *service.Patch.Type) - } - if err != nil { - return nil, fmt.Errorf("error applying merge patch: %w", err) - } - - // Deserialize the patched JSON into a new service object - var patchedService corev1.Service - if err := json.Unmarshal(patchedJSON, &patchedService); err != nil { - return nil, fmt.Errorf("error unmarshaling patched service: %w", err) - } - - return &patchedService, nil -} - -// ApplyMergePatch applies a merge patch to a HorizontalPodAutoscaler based on the merge type -func (hpa *KubernetesHorizontalPodAutoscalerSpec) ApplyMergePatch(old *autoscalingv2.HorizontalPodAutoscaler) (*autoscalingv2.HorizontalPodAutoscaler, error) { - if hpa.Patch == nil { - return old, nil - } - - var patchedJSON []byte - var err error - - // Serialize the current HPA to JSON - originalJSON, err := json.Marshal(old) - if err != nil { - return nil, fmt.Errorf("error marshaling original HorizontalPodAutoscaler: %w", err) - } - - switch { - case hpa.Patch.Type == nil || *hpa.Patch.Type == StrategicMerge: - patchedJSON, err = strategicpatch.StrategicMergePatch(originalJSON, hpa.Patch.Value.Raw, autoscalingv2.HorizontalPodAutoscaler{}) - case *hpa.Patch.Type == JSONMerge: - patchedJSON, err = jsonpatch.MergePatch(originalJSON, hpa.Patch.Value.Raw) - default: - return nil, fmt.Errorf("unsupported merge type: %s", *hpa.Patch.Type) - } - if err != nil { - return nil, fmt.Errorf("error applying merge patch: %w", err) - } - - // Deserialize the patched JSON into a new HorizontalPodAutoscaler object - var patchedHpa autoscalingv2.HorizontalPodAutoscaler - if err := json.Unmarshal(patchedJSON, &patchedHpa); err != nil { - return nil, fmt.Errorf("error unmarshaling patched HorizontalPodAutoscaler: %w", err) - } - - return &patchedHpa, nil -} - // ApplyMergePatch applies a merge patch to a PodDisruptionBudget based on the merge type func (pdb *KubernetesPodDisruptionBudgetSpec) ApplyMergePatch(old *policyv1.PodDisruptionBudget) (*policyv1.PodDisruptionBudget, error) { if pdb.Patch == nil { diff --git a/internal/infrastructure/kubernetes/proxy/resource_provider.go b/internal/infrastructure/kubernetes/proxy/resource_provider.go index 002e08a465..ca4543d072 100644 --- a/internal/infrastructure/kubernetes/proxy/resource_provider.go +++ b/internal/infrastructure/kubernetes/proxy/resource_provider.go @@ -24,6 +24,7 @@ import ( "github.com/envoyproxy/gateway/internal/infrastructure/common" "github.com/envoyproxy/gateway/internal/infrastructure/kubernetes/resource" "github.com/envoyproxy/gateway/internal/ir" + "github.com/envoyproxy/gateway/internal/utils" "github.com/envoyproxy/gateway/internal/xds/bootstrap" ) @@ -38,7 +39,7 @@ const ( // trusted CA certificate. XdsTLSCaFilepath = "/certs/ca.crt" - // XdsTLSCertFileName is the file name of the xDS server TLS certificate. + // XdsTLSCaFileName is the file name of the xDS server TLS certificate. XdsTLSCaFileName = "ca.crt" ) @@ -211,7 +212,7 @@ func (r *ResourceRender) Service() (*corev1.Service, error) { // apply merge patch to service var err error - if svc, err = envoyServiceConfig.ApplyMergePatch(svc); err != nil { + if svc, err = utils.MergeWithPatch(svc, envoyServiceConfig.Patch); err != nil { return nil, err } @@ -344,7 +345,7 @@ func (r *ResourceRender) Deployment() (*appsv1.Deployment, error) { } // apply merge patch to deployment - if deployment, err = deploymentConfig.ApplyMergePatch(deployment); err != nil { + if deployment, err = utils.MergeWithPatch(deployment, deploymentConfig.Patch); err != nil { return nil, err } @@ -413,8 +414,8 @@ func (r *ResourceRender) DaemonSet() (*appsv1.DaemonSet, error) { daemonSet.Name = r.Name() } - // apply merge patch to daemonset - if daemonSet, err = daemonSetConfig.ApplyMergePatch(daemonSet); err != nil { + // apply merge patch to DaemonSet + if daemonSet, err = utils.MergeWithPatch(daemonSet, daemonSetConfig.Patch); err != nil { return nil, err } @@ -516,8 +517,8 @@ func (r *ResourceRender) HorizontalPodAutoscaler() (*autoscalingv2.HorizontalPod hpa.Spec.ScaleTargetRef.Name = r.Name() } - hpa, err := hpaConfig.ApplyMergePatch(hpa) - if err != nil { + var err error + if hpa, err = utils.MergeWithPatch(hpa, hpaConfig.Patch); err != nil { return nil, err } diff --git a/internal/infrastructure/kubernetes/ratelimit/resource_provider.go b/internal/infrastructure/kubernetes/ratelimit/resource_provider.go index a4016ac85b..66219636ce 100644 --- a/internal/infrastructure/kubernetes/ratelimit/resource_provider.go +++ b/internal/infrastructure/kubernetes/ratelimit/resource_provider.go @@ -22,6 +22,7 @@ import ( egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" "github.com/envoyproxy/gateway/internal/infrastructure/kubernetes/resource" + "github.com/envoyproxy/gateway/internal/utils" ) // ResourceKind indicates the main resources of envoy-ratelimit, @@ -282,7 +283,8 @@ func (r *ResourceRender) Deployment() (*appsv1.Deployment, error) { // apply merge patch to deployment var err error - if deployment, err = r.rateLimitDeployment.ApplyMergePatch(deployment); err != nil { + deploymentConfig := r.rateLimitDeployment + if deployment, err = utils.MergeWithPatch(deployment, deploymentConfig.Patch); err != nil { return nil, err } @@ -335,7 +337,7 @@ func (r *ResourceRender) HorizontalPodAutoscaler() (*autoscalingv2.HorizontalPod hpa.Spec.ScaleTargetRef.Name = r.Name() } - if hpa, err = hpaConfig.ApplyMergePatch(hpa); err != nil { + if hpa, err = utils.MergeWithPatch(hpa, hpaConfig.Patch); err != nil { return nil, err } diff --git a/internal/utils/merge.go b/internal/utils/merge.go index cb5d890dd4..337147ed01 100644 --- a/internal/utils/merge.go +++ b/internal/utils/merge.go @@ -16,11 +16,23 @@ import ( egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" ) -func Merge[T client.Object](original, patch T, mergeType egv1a1.MergeType) (T, error) { +func MergeWithPatch[T client.Object](original T, patch *egv1a1.KubernetesPatchSpec) (T, error) { + if patch == nil { + return original, nil + } + + mergeType := egv1a1.StrategicMerge + if patch.Type != nil { + mergeType = *patch.Type + } + + return mergeInternal(original, patch.Value.Raw, mergeType) +} + +func mergeInternal[T client.Object](original T, patchJSON []byte, mergeType egv1a1.MergeType) (T, error) { var ( patchedJSON []byte originalJSON []byte - patchJSON []byte err error empty T ) @@ -29,13 +41,9 @@ func Merge[T client.Object](original, patch T, mergeType egv1a1.MergeType) (T, e if err != nil { return empty, fmt.Errorf("error marshaling original service: %w", err) } - patchJSON, err = json.Marshal(patch) - if err != nil { - return empty, fmt.Errorf("error marshaling original service: %w", err) - } switch mergeType { case egv1a1.StrategicMerge: - patchedJSON, err = strategicpatch.StrategicMergePatch(originalJSON, patchJSON, egv1a1.BackendTrafficPolicy{}) + patchedJSON, err = strategicpatch.StrategicMergePatch(originalJSON, patchJSON, empty) if err != nil { return empty, fmt.Errorf("error during strategic merge: %w", err) } @@ -45,7 +53,7 @@ func Merge[T client.Object](original, patch T, mergeType egv1a1.MergeType) (T, e return empty, fmt.Errorf("error during JSON merge: %w", err) } default: - return empty, fmt.Errorf("unsupported merge type: %s", mergeType) + return empty, fmt.Errorf("unsupported merge type: %v", mergeType) } res := new(T) @@ -55,3 +63,17 @@ func Merge[T client.Object](original, patch T, mergeType egv1a1.MergeType) (T, e return *res, nil } + +func Merge[T client.Object](original, patch T, mergeType egv1a1.MergeType) (T, error) { + var ( + patchJSON []byte + err error + empty T + ) + + patchJSON, err = json.Marshal(patch) + if err != nil { + return empty, fmt.Errorf("error marshaling original service: %w", err) + } + return mergeInternal(original, patchJSON, mergeType) +} From 70ccbabc8ac207374f659194a96140162722a0b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Rodr=C3=ADguez=20Hern=C3=A1ndez?= Date: Mon, 5 May 2025 11:21:32 +0200 Subject: [PATCH 13/66] Add Bitnami as an Envoy Gateway adopter (#5926) Signed-off-by: Arko Dasgupta --- site/data/adopters.yaml | 4 ++++ site/static/logos/bitnami.svg | 1 + 2 files changed, 5 insertions(+) create mode 100644 site/static/logos/bitnami.svg diff --git a/site/data/adopters.yaml b/site/data/adopters.yaml index 29e65ce992..59e0495ee7 100644 --- a/site/data/adopters.yaml +++ b/site/data/adopters.yaml @@ -67,3 +67,7 @@ adopters: logo: "/logos/cortex.png" url: "https://cortex.io/" description: "Cortex is using Envoy Gateway to manage all API traffic as a modern Kubernetes ingress replacement." + - name: "Bitnami" + logo: "/logos/bitnami.svg" + url: "https://bitnami.com/" + description: "Bitnami has added Envoy Gateway to its application catalog, making it easy for users to deploy and manage the gateway using trusted and up-to-date Helm charts across Kubernetes environments." diff --git a/site/static/logos/bitnami.svg b/site/static/logos/bitnami.svg new file mode 100644 index 0000000000..d2e321c7c8 --- /dev/null +++ b/site/static/logos/bitnami.svg @@ -0,0 +1 @@ + \ No newline at end of file From 301163e62ced303983c4a035b5ab1bd162d21ef5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 May 2025 07:45:45 +0800 Subject: [PATCH 14/66] build(deps): bump google/osv-scanner-action from 2.0.1 to 2.0.2 (#5920) Bumps [google/osv-scanner-action](https://github.com/google/osv-scanner-action) from 2.0.1 to 2.0.2. - [Release notes](https://github.com/google/osv-scanner-action/releases) - [Commits](https://github.com/google/osv-scanner-action/compare/6fc714450122bda9d00e4ad5d639ad6a39eedb1f...e69cc6c86b31f1e7e23935bbe7031b50e51082de) --- updated-dependencies: - dependency-name: google/osv-scanner-action dependency-version: 2.0.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Arko Dasgupta --- .github/workflows/license-scan.yml | 2 +- .github/workflows/osv-scanner.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/license-scan.yml b/.github/workflows/license-scan.yml index 874fc3d18f..3c9a01e0df 100644 --- a/.github/workflows/license-scan.yml +++ b/.github/workflows/license-scan.yml @@ -18,7 +18,7 @@ jobs: - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Run scanner - uses: google/osv-scanner-action/osv-scanner-action@6fc714450122bda9d00e4ad5d639ad6a39eedb1f # v2.0.1 + uses: google/osv-scanner-action/osv-scanner-action@e69cc6c86b31f1e7e23935bbe7031b50e51082de # v2.0.2 continue-on-error: true # remove this after https://github.com/google/deps.dev/issues/146 has been resolved with: scan-args: |- diff --git a/.github/workflows/osv-scanner.yml b/.github/workflows/osv-scanner.yml index 50e4936365..2784d3b64a 100644 --- a/.github/workflows/osv-scanner.yml +++ b/.github/workflows/osv-scanner.yml @@ -19,7 +19,7 @@ permissions: jobs: scan-scheduled: if: ${{ github.event_name == 'push' || github.event_name == 'schedule' }} - uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@6fc714450122bda9d00e4ad5d639ad6a39eedb1f" # v2.0.1 + uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@e69cc6c86b31f1e7e23935bbe7031b50e51082de" # v2.0.2 with: scan-args: |- --recursive @@ -32,7 +32,7 @@ jobs: scan-pr: if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }} - uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@6fc714450122bda9d00e4ad5d639ad6a39eedb1f" # v2.0.1 + uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@e69cc6c86b31f1e7e23935bbe7031b50e51082de" # v2.0.2 with: scan-args: |- --recursive From 11277ce66e732efc4372cd6f18c8b9307f35c09c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 May 2025 07:46:06 +0800 Subject: [PATCH 15/66] build(deps): bump github/codeql-action from 3.28.16 to 3.28.17 (#5919) Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.16 to 3.28.17. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/28deaeda66b76a05916b6923827895f2b14ab387...60168efe1c415ce0f5521ea06d5c2062adbeed1b) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: 3.28.17 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Arko Dasgupta --- .github/workflows/codeql.yml | 6 +++--- .github/workflows/scorecard.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 0b7b02748a..c08126cb79 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -36,14 +36,14 @@ jobs: - uses: ./tools/github-actions/setup-deps - name: Initialize CodeQL - uses: github/codeql-action/init@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/init@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/autobuild@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/analyze@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 426aa2c4ca..00bfdec63b 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -40,6 +40,6 @@ jobs: retention-days: 5 - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/upload-sarif@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: sarif_file: results.sarif From 81b85a3c3232afae32303028d638766a47cb10f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 May 2025 07:46:32 +0800 Subject: [PATCH 16/66] build(deps): bump github.com/valyala/fasthttp from 1.60.0 to 1.61.0 in /examples/preserve-case-backend in the github-com group across 1 directory (#5921) build(deps): bump github.com/valyala/fasthttp Bumps the github-com group with 1 update in the /examples/preserve-case-backend directory: [github.com/valyala/fasthttp](https://github.com/valyala/fasthttp). Updates `github.com/valyala/fasthttp` from 1.60.0 to 1.61.0 - [Release notes](https://github.com/valyala/fasthttp/releases) - [Commits](https://github.com/valyala/fasthttp/compare/v1.60.0...v1.61.0) --- updated-dependencies: - dependency-name: github.com/valyala/fasthttp dependency-version: 1.61.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-com ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Arko Dasgupta --- examples/preserve-case-backend/go.mod | 2 +- examples/preserve-case-backend/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/preserve-case-backend/go.mod b/examples/preserve-case-backend/go.mod index dd1a2ec677..d383593957 100644 --- a/examples/preserve-case-backend/go.mod +++ b/examples/preserve-case-backend/go.mod @@ -2,7 +2,7 @@ module github.com/envoyproxy/gateway-preserve-case-backend go 1.24.2 -require github.com/valyala/fasthttp v1.60.0 +require github.com/valyala/fasthttp v1.61.0 require ( github.com/andybalholm/brotli v1.1.1 // indirect diff --git a/examples/preserve-case-backend/go.sum b/examples/preserve-case-backend/go.sum index 149c4e4cd7..3281225005 100644 --- a/examples/preserve-case-backend/go.sum +++ b/examples/preserve-case-backend/go.sum @@ -4,7 +4,7 @@ github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zt github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.60.0 h1:kBRYS0lOhVJ6V+bYN8PqAHELKHtXqwq9zNMLKx1MBsw= -github.com/valyala/fasthttp v1.60.0/go.mod h1:iY4kDgV3Gc6EqhRZ8icqcmlG6bqhcDXfuHgTO4FXCvc= +github.com/valyala/fasthttp v1.61.0 h1:VV08V0AfoRaFurP1EWKvQQdPTZHiUzaVoulX1aBDgzU= +github.com/valyala/fasthttp v1.61.0/go.mod h1:wRIV/4cMwUPWnRcDno9hGnYZGh78QzODFfo1LTUhBog= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= From 3962e8c9eea093622045f462dd211695020cec50 Mon Sep 17 00:00:00 2001 From: Marcel Czaplinski <24626912+mczaplinski@users.noreply.github.com> Date: Tue, 6 May 2025 02:05:17 +0200 Subject: [PATCH 17/66] docs: fix example for http redirects page (#5830) * docs: fix example for http redirects page Signed-off-by: Marcel Czaplinski <24626912+mczaplinski@users.noreply.github.com> * chore: copy documentation patch for http redirects from v1.3 to latest Signed-off-by: Marcel Czaplinski <24626912+mczaplinski@users.noreply.github.com> --------- Signed-off-by: Marcel Czaplinski <24626912+mczaplinski@users.noreply.github.com> Signed-off-by: Arko Dasgupta --- site/content/en/latest/tasks/traffic/http-redirect.md | 10 +++++----- site/content/en/v1.3/tasks/traffic/http-redirect.md | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/site/content/en/latest/tasks/traffic/http-redirect.md b/site/content/en/latest/tasks/traffic/http-redirect.md index 4b6585ec4b..1c6dbe2757 100644 --- a/site/content/en/latest/tasks/traffic/http-redirect.md +++ b/site/content/en/latest/tasks/traffic/http-redirect.md @@ -359,7 +359,7 @@ spec: - name: http port: 80 protocol: HTTP - allowedRouters: + allowedRoutes: namespaces: from: Same - name: https @@ -371,7 +371,7 @@ spec: certificateRefs: - kind: Secret name: example-com - allowedRouters: + allowedRoutes: namespaces: from: All EOF @@ -394,7 +394,7 @@ spec: - name: http port: 80 protocol: HTTP - allowedRouters: + allowedRoutes: namespaces: from: Same - name: https @@ -406,7 +406,7 @@ spec: certificateRefs: - kind: Secret name: example-com - allowedRouters: + allowedRoutes: namespaces: from: All ``` @@ -414,7 +414,7 @@ spec: {{% /tab %}} {{< /tabpane >}} -Create the HTTPRoute to reirect requests to HTTPS and attach it to the HTTP listener using the [sectionName][] field. The HTTPRoute has to be in the same namespace as the Gateway to be accepted. +Create the HTTPRoute to redirect requests to HTTPS and attach it to the HTTP listener using the [sectionName][] field. The HTTPRoute has to be in the same namespace as the Gateway to be accepted. Do not set hostnames field in the HTTPRoute - it will accept any http request to any hostname and redirect it to the same hostname over https. {{< tabpane text=true >}} diff --git a/site/content/en/v1.3/tasks/traffic/http-redirect.md b/site/content/en/v1.3/tasks/traffic/http-redirect.md index 4b6585ec4b..1c6dbe2757 100644 --- a/site/content/en/v1.3/tasks/traffic/http-redirect.md +++ b/site/content/en/v1.3/tasks/traffic/http-redirect.md @@ -359,7 +359,7 @@ spec: - name: http port: 80 protocol: HTTP - allowedRouters: + allowedRoutes: namespaces: from: Same - name: https @@ -371,7 +371,7 @@ spec: certificateRefs: - kind: Secret name: example-com - allowedRouters: + allowedRoutes: namespaces: from: All EOF @@ -394,7 +394,7 @@ spec: - name: http port: 80 protocol: HTTP - allowedRouters: + allowedRoutes: namespaces: from: Same - name: https @@ -406,7 +406,7 @@ spec: certificateRefs: - kind: Secret name: example-com - allowedRouters: + allowedRoutes: namespaces: from: All ``` @@ -414,7 +414,7 @@ spec: {{% /tab %}} {{< /tabpane >}} -Create the HTTPRoute to reirect requests to HTTPS and attach it to the HTTP listener using the [sectionName][] field. The HTTPRoute has to be in the same namespace as the Gateway to be accepted. +Create the HTTPRoute to redirect requests to HTTPS and attach it to the HTTP listener using the [sectionName][] field. The HTTPRoute has to be in the same namespace as the Gateway to be accepted. Do not set hostnames field in the HTTPRoute - it will accept any http request to any hostname and redirect it to the same hostname over https. {{< tabpane text=true >}} From 2345f0883ed6894f46a1b64e220a831b09b6def2 Mon Sep 17 00:00:00 2001 From: Mark Winter Date: Tue, 6 May 2025 03:35:01 +0100 Subject: [PATCH 18/66] docs: Add docs for request buffering (#5910) * add docs for request buffering Signed-off-by: mark winter * add missing change Signed-off-by: mark winter --------- Signed-off-by: mark winter Signed-off-by: Arko Dasgupta --- .../latest/tasks/traffic/request-buffering.md | 207 ++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 site/content/en/latest/tasks/traffic/request-buffering.md diff --git a/site/content/en/latest/tasks/traffic/request-buffering.md b/site/content/en/latest/tasks/traffic/request-buffering.md new file mode 100644 index 0000000000..dfd5eea5b9 --- /dev/null +++ b/site/content/en/latest/tasks/traffic/request-buffering.md @@ -0,0 +1,207 @@ +--- +title: "Request Buffering" +--- + +The [Envoy buffer filter] is used to stop filter iteration and wait for a fully buffered complete request. This is useful in different situations including protecting some applications from having to deal with partial requests and high network latency. + +Enabling request buffering requires specifying a size limit for the buffer. Any requests that are larger than the limit will stop the buffering and return a HTTP 413 Content Too Large response. + +Envoy Gateway introduces a new CRD called [BackendTrafficPolicy][] that allows the user to enable request buffering. +This instantiated resource can be linked to a [Gateway][], or [HTTPRoute][]. + +If the target of the BackendTrafficPolicy is a Gateway, the request buffering will be applied to all xRoutes under that Gateway. + +## Prerequisites + +{{< boilerplate prerequisites >}} + +## Configuration + +Enable request buffering by creating an [BackendTrafficPolicy][BackendTrafficPolicy] and attaching it to the example HTTPRoute. + +### HTTPRoute + +{{< tabpane text=true >}} +{{% tab header="Apply from stdin" %}} + +```shell +cat <}} + +A HTTPRoute resource is created for the `/foo` path prefix. The `request-buffer` BackendTrafficPolicy has been created and targeted HTTPRoute foo to enable request buffering. A small buffer limit of `4` bytes is purposely chosen to make testing easier. + +Verify the HTTPRoute configuration and status: + +```shell +kubectl get httproute/foo -o yaml +``` + +Verify the BackendTrafficPolicy configuration: + +```shell +kubectl get backendtrafficpolicy/request-buffer -o yaml +``` + +## Testing + +Ensure the `GATEWAY_HOST` environment variable from the [Quickstart](../../quickstart) is set. If not, follow the +Quickstart instructions to set the variable. + +```shell +echo $GATEWAY_HOST +``` + +### HTTPRoute + +We will try sending a request with an empty json object that is less than the buffer limit of 4 bytes + +```shell +curl -H "Host: www.example.com" "http://${GATEWAY_HOST}/foo" -XPOST -d '{}' +``` + +We will see the following output. The `Content-Length` header will be added by the buffer filter. + +``` +{ + "path": "/foo", + "host": "www.example.com", + "method": "POST", + "proto": "HTTP/1.1", + "headers": { + "Accept": [ + "*/*" + ], + "Content-Length": [ + "2" + ], + "Content-Type": [ + "application/x-www-form-urlencoded" + ], + "User-Agent": [ + "curl/8.7.1" + ], + "X-Envoy-External-Address": [ + "127.0.0.1" + ], + "X-Forwarded-For": [ + "10.244.0.2" + ], + "X-Forwarded-Proto": [ + "http" + ], + "X-Request-Id": [ + "daf7067e-a9e5-48da-86d2-6f5d9ccfb57e" + ] + }, + "namespace": "default", + "ingress": "", + "service": "", + "pod": "backend-869c8646c5-9vm4l" +} +``` + +Next we will try sending a json object that is larger than 4 bytes. We will also write the status code to make it clear. + +```shell +curl -H "Host: www.example.com" "http://${GATEWAY_HOST}/foo" -XPOST -d '{"key": "value"}' -w "\nStatus Code: %{http_code}" +``` + +We will now see that sending a payload of `{"key": "value"}` which is larger than the request buffer limit of 4 bytes returns a +HTTP 413 Payload Too Large response + +``` +Payload Too Large +Status Code: 413 +``` + +## Clean-Up + +Follow the steps from the [Quickstart](../../quickstart) to uninstall Envoy Gateway and the example manifest. + +Delete the BackendTrafficPolicy and HTTPRoute: + +```shell +kubectl delete httproute/foo +kubectl delete backendtrafficpolicy/request-buffer +``` + +[Envoy buffer filter]: https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/buffer_filter +[BackendTrafficPolicy]: ../../../api/extension_types#backendtrafficpolicy +[Gateway]: https://gateway-api.sigs.k8s.io/api-types/gateway/ +[HTTPRoute]: https://gateway-api.sigs.k8s.io/api-types/httproute/ From 603af40bb1f100672ac369b81609da40785d2aa2 Mon Sep 17 00:00:00 2001 From: "Huabing (Robin) Zhao" Date: Tue, 6 May 2025 11:46:09 +0800 Subject: [PATCH 19/66] feat: support configuring tls for dynamic resolver backend (#5867) * support configuring tls for dynamic resolver backend Signed-off-by: Huabing (Robin) Zhao Signed-off-by: Arko Dasgupta --- api/v1alpha1/backend_types.go | 4 +- .../gateway.envoyproxy.io_backends.yaml | 11 ++ .../gateway.envoyproxy.io_backends.yaml | 11 ++ internal/gatewayapi/backendtlspolicy.go | 111 ++++++++++--- internal/gatewayapi/ext_service.go | 5 + internal/gatewayapi/listener.go | 5 + internal/gatewayapi/route.go | 1 + .../httproute-dynamic-resolver.in.yaml | 62 ++++++- .../httproute-dynamic-resolver.out.yaml | 87 +++++++++- internal/ir/xds.go | 7 +- internal/ir/zz_generated.deepcopy.go | 5 + internal/provider/kubernetes/controller.go | 50 +++++- internal/provider/kubernetes/indexers.go | 51 ++++++ internal/xds/translator/cluster.go | 22 ++- .../xds-ir/http-route-dynamic-resolver.yaml | 5 + .../http-route-dynamic-resolver.clusters.yaml | 12 ++ .../http-route-dynamic-resolver.secrets.yaml | 4 + internal/xds/translator/translator.go | 45 +++--- site/content/en/latest/api/extension_types.md | 1 + test/cel-validation/backend_test.go | 25 +++ ...ith-dynamic-resolver-backend-with-tls.yaml | 151 ++++++++++++++++++ ...httproute_with_dynamic_resolver_backend.go | 25 ++- test/helm/gateway-crds-helm/all.out.yaml | 11 ++ .../envoy-gateway-crds.out.yaml | 11 ++ 24 files changed, 657 insertions(+), 65 deletions(-) create mode 100644 internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.secrets.yaml create mode 100644 test/e2e/testdata/httproute-with-dynamic-resolver-backend-with-tls.yaml diff --git a/api/v1alpha1/backend_types.go b/api/v1alpha1/backend_types.go index e0c626a9c4..544ff79944 100644 --- a/api/v1alpha1/backend_types.go +++ b/api/v1alpha1/backend_types.go @@ -115,6 +115,7 @@ type UnixSocket struct { // BackendSpec describes the desired state of BackendSpec. // +kubebuilder:validation:XValidation:rule="self.type != 'DynamicResolver' || !has(self.endpoints) && !has(self.appProtocols)",message="DynamicResolver type cannot have endpoints and appProtocols specified" +// +kubebuilder:validation:XValidation:rule="has(self.tls) ? self.type == 'DynamicResolver' : true",message="TLS settings can only be specified for DynamicResolver backends" type BackendSpec struct { // Type defines the type of the backend. Defaults to "Endpoints" // @@ -148,12 +149,13 @@ type BackendSpec struct { // Only supported for DynamicResolver backends. // // +optional - // +notImplementedHide TLS *BackendTLSSettings `json:"tls,omitempty"` } // BackendTLSSettings holds the TLS settings for the backend. // Only used for DynamicResolver backends. +// +kubebuilder:validation:XValidation:message="must not contain both CACertificateRefs and WellKnownCACertificates",rule="!(has(self.caCertificateRefs) && size(self.caCertificateRefs) > 0 && has(self.wellKnownCACertificates) && self.wellKnownCACertificates != \"\")" +// +kubebuilder:validation:XValidation:message="must specify either CACertificateRefs or WellKnownCACertificates",rule="(has(self.caCertificateRefs) && size(self.caCertificateRefs) > 0 || has(self.wellKnownCACertificates) && self.wellKnownCACertificates != \"\")" type BackendTLSSettings struct { // CACertificateRefs contains one or more references to Kubernetes objects that // contain TLS certificates of the Certificate Authorities that can be used diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backends.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backends.yaml index 560e58147f..6cf3e37a38 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backends.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backends.yaml @@ -213,6 +213,15 @@ spec: - System type: string type: object + x-kubernetes-validations: + - message: must not contain both CACertificateRefs and WellKnownCACertificates + rule: '!(has(self.caCertificateRefs) && size(self.caCertificateRefs) + > 0 && has(self.wellKnownCACertificates) && self.wellKnownCACertificates + != "")' + - message: must specify either CACertificateRefs or WellKnownCACertificates + rule: (has(self.caCertificateRefs) && size(self.caCertificateRefs) + > 0 || has(self.wellKnownCACertificates) && self.wellKnownCACertificates + != "") type: default: Endpoints description: Type defines the type of the backend. Defaults to "Endpoints" @@ -225,6 +234,8 @@ spec: - message: DynamicResolver type cannot have endpoints and appProtocols specified rule: self.type != 'DynamicResolver' || !has(self.endpoints) && !has(self.appProtocols) + - message: TLS settings can only be specified for DynamicResolver backends + rule: 'has(self.tls) ? self.type == ''DynamicResolver'' : true' status: description: Status defines the current status of Backend. properties: diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backends.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backends.yaml index 1b631d38a3..cf4a6eb50b 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backends.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backends.yaml @@ -212,6 +212,15 @@ spec: - System type: string type: object + x-kubernetes-validations: + - message: must not contain both CACertificateRefs and WellKnownCACertificates + rule: '!(has(self.caCertificateRefs) && size(self.caCertificateRefs) + > 0 && has(self.wellKnownCACertificates) && self.wellKnownCACertificates + != "")' + - message: must specify either CACertificateRefs or WellKnownCACertificates + rule: (has(self.caCertificateRefs) && size(self.caCertificateRefs) + > 0 || has(self.wellKnownCACertificates) && self.wellKnownCACertificates + != "") type: default: Endpoints description: Type defines the type of the backend. Defaults to "Endpoints" @@ -224,6 +233,8 @@ spec: - message: DynamicResolver type cannot have endpoints and appProtocols specified rule: self.type != 'DynamicResolver' || !has(self.endpoints) && !has(self.appProtocols) + - message: TLS settings can only be specified for DynamicResolver backends + rule: 'has(self.tls) ? self.type == ''DynamicResolver'' : true' status: description: Status defines the current status of Backend. properties: diff --git a/internal/gatewayapi/backendtlspolicy.go b/internal/gatewayapi/backendtlspolicy.go index e39fc962b3..c6bc0b8bba 100644 --- a/internal/gatewayapi/backendtlspolicy.go +++ b/internal/gatewayapi/backendtlspolicy.go @@ -21,12 +21,79 @@ import ( "github.com/envoyproxy/gateway/internal/ir" ) -func (t *Translator) applyBackendTLSSetting(backendRef gwapiv1.BackendObjectReference, backendNamespace string, parent gwapiv1a2.ParentReference, resources *resource.Resources, envoyProxy *egv1a1.EnvoyProxy) (*ir.TLSUpstreamConfig, error) { +func (t *Translator) applyBackendTLSSetting( + backendRef gwapiv1.BackendObjectReference, + backendNamespace string, + parent gwapiv1a2.ParentReference, + resources *resource.Resources, + envoyProxy *egv1a1.EnvoyProxy, + isDynamicResolver bool, +) (*ir.TLSUpstreamConfig, error) { + var ( + err error + tlsBundle *ir.TLSUpstreamConfig + ) + + // If the destination is a dynamic resolver, we need to use the CACertificateRefs from the backend object + // and not from the BackendTLSPolicy. This is because the BackendTLSPolicy requires a valid hostname, and + // dynamic resolvers's hostname is not fixed. + if isDynamicResolver { + if tlsBundle, err = t.processBackendTLSConfig(backendRef, backendNamespace, resources); err != nil { + return nil, err + } + return t.applyEnvoyProxyBackendTLSSetting(tlsBundle, resources, envoyProxy) + } + upstreamConfig, policy, err := t.processBackendTLSPolicy(backendRef, backendNamespace, parent, resources) if err != nil { return nil, err } - return t.applyEnvoyProxyBackendTLSSetting(policy, upstreamConfig, resources, parent, envoyProxy) + // TODO: zhaohuabing is it correct to surface the EnvoyProxy TLS error to the policy? + // We probably should just remove this, the TLS error is already reported in the route status as "InvalidBackendTLS", + // and the BackendTLSPolicy is configured correctly. + if tlsBundle, err = t.applyEnvoyProxyBackendTLSSetting(upstreamConfig, resources, envoyProxy); err != nil { + status.SetTranslationErrorForPolicyAncestors(&policy.Status, + []gwapiv1a2.ParentReference{parent}, + t.GatewayControllerName, + policy.Generation, + status.Error2ConditionMsg(err)) + return nil, err + } + return tlsBundle, nil +} + +func (t *Translator) processBackendTLSConfig( + backendRef gwapiv1.BackendObjectReference, + backendNamespace string, + resources *resource.Resources, +) (*ir.TLSUpstreamConfig, error) { + backend := resources.GetBackend(backendNamespace, string(backendRef.Name)) + if backend == nil { + return nil, fmt.Errorf("backend %s not found", backendRef.Name) + } + if backend.Spec.TLS == nil { + return nil, nil + } + + tlsBundle := &ir.TLSUpstreamConfig{ + UseSystemTrustStore: ptr.Deref(backend.Spec.TLS.WellKnownCACertificates, "") == gwapiv1a3.WellKnownCACertificatesSystem, + } + if tlsBundle.UseSystemTrustStore { + tlsBundle.CACertificate = &ir.TLSCACertificate{ + Name: fmt.Sprintf("%s/%s-ca", backend.Name, backend.Namespace), + } + return tlsBundle, nil + } + + caCert, err := getCaCertsFromCARefs(backend.Spec.TLS.CACertificateRefs, resources) + if err != nil { + return nil, err + } + tlsBundle.CACertificate = &ir.TLSCACertificate{ + Certificate: caCert, + Name: fmt.Sprintf("%s/%s-ca", backend.Name, backend.Namespace), + } + return tlsBundle, nil } func (t *Translator) processBackendTLSPolicy( @@ -58,7 +125,7 @@ func (t *Translator) processBackendTLSPolicy( return tlsBundle, policy, nil } -func (t *Translator) applyEnvoyProxyBackendTLSSetting(policy *gwapiv1a3.BackendTLSPolicy, tlsConfig *ir.TLSUpstreamConfig, resources *resource.Resources, parent gwapiv1a2.ParentReference, ep *egv1a1.EnvoyProxy) (*ir.TLSUpstreamConfig, error) { +func (t *Translator) applyEnvoyProxyBackendTLSSetting(tlsConfig *ir.TLSUpstreamConfig, resources *resource.Resources, ep *egv1a1.EnvoyProxy) (*ir.TLSUpstreamConfig, error) { if ep == nil || ep.Spec.BackendTLS == nil || tlsConfig == nil { return tlsConfig, nil } @@ -86,17 +153,10 @@ func (t *Translator) applyEnvoyProxyBackendTLSSetting(policy *gwapiv1a3.BackendT } if ep.Spec.BackendTLS != nil && ep.Spec.BackendTLS.ClientCertificateRef != nil { ns := string(ptr.Deref(ep.Spec.BackendTLS.ClientCertificateRef.Namespace, "")) - ancestorRefs := []gwapiv1a2.ParentReference{ - parent, - } + var err error if ns != ep.Namespace { err = fmt.Errorf("ClientCertificateRef Secret is not located in the same namespace as Envoyproxy. Secret namespace: %s does not match Envoyproxy namespace: %s", ns, ep.Namespace) - status.SetTranslationErrorForPolicyAncestors(&policy.Status, - ancestorRefs, - t.GatewayControllerName, - policy.Generation, - status.Error2ConditionMsg(err)) return tlsConfig, err } secret := resources.GetSecret(ns, string(ep.Spec.BackendTLS.ClientCertificateRef.Name)) @@ -112,12 +172,6 @@ func (t *Translator) applyEnvoyProxyBackendTLSSetting(policy *gwapiv1a3.BackendT Name: ep.Name, }.String(), ) - status.SetTranslationErrorForPolicyAncestors(&policy.Status, - ancestorRefs, - t.GatewayControllerName, - policy.Generation, - status.Error2ConditionMsg(err), - ) return tlsConfig, err } tlsConf := irTLSConfigs(secret) @@ -161,7 +215,7 @@ func getBackendTLSPolicy( func getBackendTLSBundle(backendTLSPolicy *gwapiv1a3.BackendTLSPolicy, resources *resource.Resources) (*ir.TLSUpstreamConfig, error) { tlsBundle := &ir.TLSUpstreamConfig{ - SNI: string(backendTLSPolicy.Spec.Validation.Hostname), + SNI: ptr.To(string(backendTLSPolicy.Spec.Validation.Hostname)), UseSystemTrustStore: ptr.Deref(backendTLSPolicy.Spec.Validation.WellKnownCACertificates, "") == gwapiv1a3.WellKnownCACertificatesSystem, } if tlsBundle.UseSystemTrustStore { @@ -171,8 +225,20 @@ func getBackendTLSBundle(backendTLSPolicy *gwapiv1a3.BackendTLSPolicy, resources return tlsBundle, nil } + caCert, err := getCaCertsFromCARefs(backendTLSPolicy.Spec.Validation.CACertificateRefs, resources) + if err != nil { + return nil, err + } + tlsBundle.CACertificate = &ir.TLSCACertificate{ + Certificate: caCert, + Name: fmt.Sprintf("%s/%s-ca", backendTLSPolicy.Name, backendTLSPolicy.Namespace), + } + return tlsBundle, nil +} + +func getCaCertsFromCARefs(caCertificates []gwapiv1.LocalObjectReference, resources *resource.Resources) ([]byte, error) { ca := "" - for _, caRef := range backendTLSPolicy.Spec.Validation.CACertificateRefs { + for _, caRef := range caCertificates { kind := string(caRef.Kind) switch kind { @@ -208,12 +274,7 @@ func getBackendTLSBundle(backendTLSPolicy *gwapiv1a3.BackendTLSPolicy, resources if ca == "" { return nil, fmt.Errorf("no ca found in referred configmaps") } - tlsBundle.CACertificate = &ir.TLSCACertificate{ - Certificate: []byte(ca), - Name: fmt.Sprintf("%s/%s-ca", backendTLSPolicy.Name, backendTLSPolicy.Namespace), - } - - return tlsBundle, nil + return []byte(ca), nil } func getAncestorRefs(policy *gwapiv1a3.BackendTLSPolicy) []gwapiv1a2.ParentReference { diff --git a/internal/gatewayapi/ext_service.go b/internal/gatewayapi/ext_service.go index 899e84eebe..2665497d32 100644 --- a/internal/gatewayapi/ext_service.go +++ b/internal/gatewayapi/ext_service.go @@ -108,6 +108,10 @@ func (t *Translator) processExtServiceDestination( return nil, fmt.Errorf("resource %s of type Backend cannot be used since Backend is disabled in Envoy Gateway configuration", string(backendRef.Name)) } ds = t.processBackendDestinationSetting(settingName, backendRef.BackendObjectReference, backendNamespace, protocol, resources) + // Dynamic resolver destinations are not supported for none-route destinations + if ds.IsDynamicResolver { + return nil, errors.New("dynamic resolver destinations are not supported") + } } if ds == nil { @@ -137,6 +141,7 @@ func (t *Translator) processExtServiceDestination( }, resources, envoyProxy, + false, ) if err != nil { return nil, err diff --git a/internal/gatewayapi/listener.go b/internal/gatewayapi/listener.go index da0ad0def5..9f2fb5de26 100644 --- a/internal/gatewayapi/listener.go +++ b/internal/gatewayapi/listener.go @@ -6,6 +6,7 @@ package gatewayapi import ( + "errors" "fmt" "math" "strings" @@ -753,6 +754,10 @@ func (t *Translator) processBackendRefs(name string, backendCluster egv1a1.Backe return nil, nil, err } ds := t.processBackendDestinationSetting(name, ref.BackendObjectReference, ns, ir.TCP, resources) + // Dynamic resolver destinations are not supported for none-route destinations + if ds.IsDynamicResolver { + return nil, nil, errors.New("dynamic resolver destinations are not supported") + } result = append(result, ds) default: return nil, nil, fmt.Errorf("unsupported kind for backendRefs: %s", kind) diff --git a/internal/gatewayapi/route.go b/internal/gatewayapi/route.go index 0d9d2a3ed5..b98e81a465 100644 --- a/internal/gatewayapi/route.go +++ b/internal/gatewayapi/route.go @@ -1418,6 +1418,7 @@ func (t *Translator) processDestination(name string, backendRefContext BackendRe }, resources, envoyProxy, + ds.IsDynamicResolver, ) if tlsErr != nil { return nil, status.NewRouteStatusError(tlsErr, status.RouteReasonInvalidBackendTLS) diff --git a/internal/gatewayapi/testdata/httproute-dynamic-resolver.in.yaml b/internal/gatewayapi/testdata/httproute-dynamic-resolver.in.yaml index 3cd1bbb8ef..03f9e16bf9 100644 --- a/internal/gatewayapi/testdata/httproute-dynamic-resolver.in.yaml +++ b/internal/gatewayapi/testdata/httproute-dynamic-resolver.in.yaml @@ -30,12 +30,70 @@ httpRoutes: - backendRefs: - group: gateway.envoyproxy.io kind: Backend - name: backend-1 + name: backend-with-custom-ca +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend-with-system-ca backends: - apiVersion: gateway.envoyproxy.io/v1alpha1 kind: Backend metadata: - name: backend-1 + name: backend-with-custom-ca namespace: default spec: type: DynamicResolver + tls: + caCertificateRefs: + - name: ca-cmap + group: "" + kind: ConfigMap +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: Backend + metadata: + name: backend-with-system-ca + namespace: default + spec: + type: DynamicResolver + tls: + wellKnownCACertificates: "System" +configMaps: +- apiVersion: v1 + kind: ConfigMap + metadata: + name: ca-cmap + namespace: backends + data: + ca.crt: | + -----BEGIN CERTIFICATE----- + MIIDJzCCAg+gAwIBAgIUAl6UKIuKmzte81cllz5PfdN2IlIwDQYJKoZIhvcNAQEL + BQAwIzEQMA4GA1UEAwwHbXljaWVudDEPMA0GA1UECgwGa3ViZWRiMB4XDTIzMTAw + MjA1NDE1N1oXDTI0MTAwMTA1NDE1N1owIzEQMA4GA1UEAwwHbXljaWVudDEPMA0G + A1UECgwGa3ViZWRiMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwSTc + 1yj8HW62nynkFbXo4VXKv2jC0PM7dPVky87FweZcTKLoWQVPQE2p2kLDK6OEszmM + yyr+xxWtyiveremrWqnKkNTYhLfYPhgQkczib7eUalmFjUbhWdLvHakbEgCodn3b + kz57mInX2VpiDOKg4kyHfiuXWpiBqrCx0KNLpxo3DEQcFcsQTeTHzh4752GV04RU + Ti/GEWyzIsl4Rg7tGtAwmcIPgUNUfY2Q390FGqdH4ahn+mw/6aFbW31W63d9YJVq + ioyOVcaMIpM5B/c7Qc8SuhCI1YGhUyg4cRHLEw5VtikioyE3X04kna3jQAj54YbR + bpEhc35apKLB21HOUQIDAQABo1MwUTAdBgNVHQ4EFgQUyvl0VI5vJVSuYFXu7B48 + 6PbMEAowHwYDVR0jBBgwFoAUyvl0VI5vJVSuYFXu7B486PbMEAowDwYDVR0TAQH/ + BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAMLxrgFVMuNRq2wAwcBt7SnNR5Cfz + 2MvXq5EUmuawIUi9kaYjwdViDREGSjk7JW17vl576HjDkdfRwi4E28SydRInZf6J + i8HZcZ7caH6DxR335fgHVzLi5NiTce/OjNBQzQ2MJXVDd8DBmG5fyatJiOJQ4bWE + A7FlP0RdP3CO3GWE0M5iXOB2m1qWkE2eyO4UHvwTqNQLdrdAXgDQlbam9e4BG3Gg + d/6thAkWDbt/QNT+EJHDCvhDRKh1RuGHyg+Y+/nebTWWrFWsktRrbOoHCZiCpXI1 + 3eXE6nt0YkgtDxG22KqnhpAg9gUSs2hlhoxyvkzyF0mu6NhPlwAgnq7+/Q== + -----END CERTIFICATE----- diff --git a/internal/gatewayapi/testdata/httproute-dynamic-resolver.out.yaml b/internal/gatewayapi/testdata/httproute-dynamic-resolver.out.yaml index 2fa2a8b335..8164485596 100644 --- a/internal/gatewayapi/testdata/httproute-dynamic-resolver.out.yaml +++ b/internal/gatewayapi/testdata/httproute-dynamic-resolver.out.yaml @@ -3,9 +3,31 @@ backends: kind: Backend metadata: creationTimestamp: null - name: backend-1 + name: backend-with-custom-ca namespace: default spec: + tls: + caCertificateRefs: + - group: "" + kind: ConfigMap + name: ca-cmap + type: DynamicResolver + status: + conditions: + - lastTransitionTime: null + message: The Backend was accepted + reason: Accepted + status: "True" + type: Accepted +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: Backend + metadata: + creationTimestamp: null + name: backend-with-system-ca + namespace: default + spec: + tls: + wellKnownCACertificates: System type: DynamicResolver status: conditions: @@ -32,7 +54,7 @@ gateways: protocol: HTTP status: listeners: - - attachedRoutes: 1 + - attachedRoutes: 2 conditions: - lastTransitionTime: null message: Sending translated listener configuration to the data plane @@ -73,7 +95,43 @@ httpRoutes: - backendRefs: - group: gateway.envoyproxy.io kind: Backend - name: backend-1 + name: backend-with-custom-ca + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-2 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend-with-system-ca status: parents: - conditions: @@ -135,6 +193,11 @@ xdsIR: settings: - isDynamicResolver: true name: httproute/default/httproute-1/rule/0/backend/0 + tls: + alpnProtocols: null + caCertificate: + certificate: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURKekNDQWcrZ0F3SUJBZ0lVQWw2VUtJdUttenRlODFjbGx6NVBmZE4ySWxJd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0l6RVFNQTRHQTFVRUF3d0hiWGxqYVdWdWRERVBNQTBHQTFVRUNnd0dhM1ZpWldSaU1CNFhEVEl6TVRBdwpNakExTkRFMU4xb1hEVEkwTVRBd01UQTFOREUxTjFvd0l6RVFNQTRHQTFVRUF3d0hiWGxqYVdWdWRERVBNQTBHCkExVUVDZ3dHYTNWaVpXUmlNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdTVGMKMXlqOEhXNjJueW5rRmJYbzRWWEt2MmpDMFBNN2RQVmt5ODdGd2VaY1RLTG9XUVZQUUUycDJrTERLNk9Fc3ptTQp5eXIreHhXdHlpdmVyZW1yV3FuS2tOVFloTGZZUGhnUWtjemliN2VVYWxtRmpVYmhXZEx2SGFrYkVnQ29kbjNiCmt6NTdtSW5YMlZwaURPS2c0a3lIZml1WFdwaUJxckN4MEtOTHB4bzNERVFjRmNzUVRlVEh6aDQ3NTJHVjA0UlUKVGkvR0VXeXpJc2w0Umc3dEd0QXdtY0lQZ1VOVWZZMlEzOTBGR3FkSDRhaG4rbXcvNmFGYlczMVc2M2Q5WUpWcQppb3lPVmNhTUlwTTVCL2M3UWM4U3VoQ0kxWUdoVXlnNGNSSExFdzVWdGlraW95RTNYMDRrbmEzalFBajU0WWJSCmJwRWhjMzVhcEtMQjIxSE9VUUlEQVFBQm8xTXdVVEFkQmdOVkhRNEVGZ1FVeXZsMFZJNXZKVlN1WUZYdTdCNDgKNlBiTUVBb3dId1lEVlIwakJCZ3dGb0FVeXZsMFZJNXZKVlN1WUZYdTdCNDg2UGJNRUFvd0R3WURWUjBUQVFILwpCQVV3QXdFQi96QU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFNTHhyZ0ZWTXVOUnEyd0F3Y0J0N1NuTlI1Q2Z6CjJNdlhxNUVVbXVhd0lVaTlrYVlqd2RWaURSRUdTams3SlcxN3ZsNTc2SGpEa2RmUndpNEUyOFN5ZFJJblpmNkoKaThIWmNaN2NhSDZEeFIzMzVmZ0hWekxpNU5pVGNlL09qTkJRelEyTUpYVkRkOERCbUc1ZnlhdEppT0pRNGJXRQpBN0ZsUDBSZFAzQ08zR1dFME01aVhPQjJtMXFXa0UyZXlPNFVIdndUcU5RTGRyZEFYZ0RRbGJhbTllNEJHM0dnCmQvNnRoQWtXRGJ0L1FOVCtFSkhEQ3ZoRFJLaDFSdUdIeWcrWSsvbmViVFdXckZXc2t0UnJiT29IQ1ppQ3BYSTEKM2VYRTZudDBZa2d0RHhHMjJLcW5ocEFnOWdVU3MyaGxob3h5dmt6eUYwbXU2TmhQbHdBZ25xNysvUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + name: backend-with-custom-ca/default-ca weight: 1 hostname: gateway.envoyproxy.io isHTTP2: false @@ -143,6 +206,24 @@ xdsIR: name: httproute-1 namespace: default name: httproute/default/httproute-1/rule/0/match/-1/gateway_envoyproxy_io + - destination: + name: httproute/default/httproute-2/rule/0 + settings: + - isDynamicResolver: true + name: httproute/default/httproute-2/rule/0/backend/0 + tls: + alpnProtocols: null + caCertificate: + name: backend-with-system-ca/default-ca + useSystemTrustStore: true + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-2 + namespace: default + name: httproute/default/httproute-2/rule/0/match/-1/gateway_envoyproxy_io readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/ir/xds.go b/internal/ir/xds.go index db9dfc4e02..777e33eb9c 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -2848,7 +2848,7 @@ type BackOffPolicy struct { // TLSUpstreamConfig contains sni and ca file in []byte format. // +k8s:deepcopy-gen=true type TLSUpstreamConfig struct { - SNI string `json:"sni,omitempty" yaml:"sni,omitempty"` + SNI *string `json:"sni,omitempty" yaml:"sni,omitempty"` UseSystemTrustStore bool `json:"useSystemTrustStore,omitempty" yaml:"useSystemTrustStore,omitempty"` CACertificate *TLSCACertificate `json:"caCertificate,omitempty" yaml:"caCertificate,omitempty"` TLSConfig `json:",inline"` @@ -2856,8 +2856,9 @@ type TLSUpstreamConfig struct { func (t *TLSUpstreamConfig) ToTLSConfig() (*tls.Config, error) { // nolint:gosec - tlsConfig := &tls.Config{ - ServerName: t.SNI, + tlsConfig := &tls.Config{} + if t.SNI != nil { + tlsConfig.ServerName = *t.SNI } if t.MinVersion != nil { tlsConfig.MinVersion = t.MinVersion.Int() diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index 39f4de5435..5b27f1e48f 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -3577,6 +3577,11 @@ func (in *TLSInspectorConfig) DeepCopy() *TLSInspectorConfig { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TLSUpstreamConfig) DeepCopyInto(out *TLSUpstreamConfig) { *out = *in + if in.SNI != nil { + in, out := &in.SNI, &out.SNI + *out = new(string) + **out = **in + } if in.CACertificate != nil { in, out := &in.CACertificate, &out.CACertificate *out = new(TLSCACertificate) diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go index 0e613e80f1..629cb41923 100644 --- a/internal/provider/kubernetes/controller.go +++ b/internal/provider/kubernetes/controller.go @@ -407,6 +407,7 @@ func (r *gatewayAPIReconciler) managedGatewayClasses(ctx context.Context) ([]*gw // - ServiceImports // - EndpointSlices // - Backends +// - CACertificateRefs in the Backends func (r *gatewayAPIReconciler) processBackendRefs(ctx context.Context, gwcResource *resource.Resources, resourceMappings *resourceMappings) { for backendRef := range resourceMappings.allAssociatedBackendRefs { backendRefKind := gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService) @@ -463,6 +464,49 @@ func (r *gatewayAPIReconciler) processBackendRefs(ctx context.Context, gwcResour "name", string(backendRef.Name)) } } + + if backend.Spec.TLS != nil && backend.Spec.TLS.CACertificateRefs != nil { + for _, caCertRef := range backend.Spec.TLS.CACertificateRefs { + // if kind is not Secret or ConfigMap, we skip early to avoid further calculation overhead + if string(caCertRef.Kind) == resource.KindConfigMap || + string(caCertRef.Kind) == resource.KindSecret { + + var err error + caRefNew := gwapiv1.SecretObjectReference{ + Group: gatewayapi.GroupPtr(string(caCertRef.Group)), + Kind: gatewayapi.KindPtr(string(caCertRef.Kind)), + Name: caCertRef.Name, + Namespace: gatewayapi.NamespacePtr(backend.Namespace), + } + switch string(caCertRef.Kind) { + case resource.KindConfigMap: + err = r.processConfigMapRef( + ctx, + resourceMappings, + gwcResource, + resource.KindBackendTLSPolicy, + backend.Namespace, + backend.Name, + caRefNew) + + case resource.KindSecret: + err = r.processSecretRef( + ctx, + resourceMappings, + gwcResource, + resource.KindBackendTLSPolicy, + backend.Namespace, + backend.Name, + caRefNew) + } + if err != nil { + r.log.Error(err, + "failed to process CACertificateRef for Backend", + "backend", backend, "caCertificateRef", caCertRef.Name) + } + } + } + } } // Retrieve the EndpointSlices associated with the Service and ServiceImport @@ -776,7 +820,7 @@ func (r *gatewayAPIReconciler) processCtpConfigMapRefs( policy.Name, caCertRef); err != nil { r.log.Error(err, - "failed to process CACertificateRef for SecurityPolicy", + "failed to process CACertificateRef for ClientTrafficPolicy", "policy", policy, "caCertificateRef", caCertRef.Name) } } @@ -1483,6 +1527,10 @@ func (r *gatewayAPIReconciler) watchResources(ctx context.Context, mgr manager.M backendPredicates...)); err != nil { return err } + + if err := addBackendIndexers(ctx, mgr); err != nil { + return err + } } // Watch Node CRUDs to update Gateway Address exposed by Service of type NodePort. diff --git a/internal/provider/kubernetes/indexers.go b/internal/provider/kubernetes/indexers.go index 7f90d87628..cd43fc34ba 100644 --- a/internal/provider/kubernetes/indexers.go +++ b/internal/provider/kubernetes/indexers.go @@ -42,6 +42,8 @@ const ( configMapSecurityPolicyIndex = "configMapSecurityPolicyIndex" configMapCtpIndex = "configMapCtpIndex" secretCtpIndex = "secretCtpIndex" + configMapBackendIndex = "configMapBackendIndex" + secretBackendIndex = "secretBackendIndex" secretBtlsIndex = "secretBtlsIndex" configMapBtlsIndex = "configMapBtlsIndex" backendEnvoyExtensionPolicyIndex = "backendEnvoyExtensionPolicyIndex" @@ -708,6 +710,55 @@ func secretCtpIndexFunc(rawObj client.Object) []string { return secretReferences } +// addBackendIndexers adds indexing on Backend, for ConfigMap or Secret objects that are +// referenced in Backend objects. +func addBackendIndexers(ctx context.Context, mgr manager.Manager) error { + if err := mgr.GetFieldIndexer().IndexField(ctx, &egv1a1.Backend{}, configMapBackendIndex, configMapBackendIndexFunc); err != nil { + return err + } + if err := mgr.GetFieldIndexer().IndexField(ctx, &egv1a1.Backend{}, secretBackendIndex, secretBackendIndexFunc); err != nil { + return err + } + + return nil +} + +func configMapBackendIndexFunc(rawObj client.Object) []string { + backend := rawObj.(*egv1a1.Backend) + var configMapReferences []string + if backend.Spec.TLS != nil && backend.Spec.TLS.CACertificateRefs != nil { + for _, caCertRef := range backend.Spec.TLS.CACertificateRefs { + if caCertRef.Kind == resource.KindConfigMap { + configMapReferences = append(configMapReferences, + types.NamespacedName{ + Namespace: backend.Namespace, + Name: string(caCertRef.Name), + }.String(), + ) + } + } + } + return configMapReferences +} + +func secretBackendIndexFunc(rawObj client.Object) []string { + backend := rawObj.(*egv1a1.Backend) + var secretReferences []string + if backend.Spec.TLS != nil && backend.Spec.TLS.CACertificateRefs != nil { + for _, caCertRef := range backend.Spec.TLS.CACertificateRefs { + if caCertRef.Kind == resource.KindSecret { + secretReferences = append(secretReferences, + types.NamespacedName{ + Namespace: backend.Namespace, + Name: string(caCertRef.Name), + }.String(), + ) + } + } + } + return secretReferences +} + // addBtpIndexers adds indexing on BackendTrafficPolicy, for ConfigMap objects that are // referenced in BackendTrafficPolicy objects. This helps in querying for BackendTrafficPolies that are // affected by a particular ConfigMap CRUD. diff --git a/internal/xds/translator/cluster.go b/internal/xds/translator/cluster.go index 0d240527e4..4b9e01ea51 100644 --- a/internal/xds/translator/cluster.go +++ b/internal/xds/translator/cluster.go @@ -193,15 +193,21 @@ func buildXdsCluster(args *xdsClusterArgs) (*buildClusterResult, error) { socket = buildProxyProtocolSocket(args.proxyProtocol, socket) } matchName := fmt.Sprintf("%s/tls/%d", args.name, i) - cluster.TransportSocketMatches = append(cluster.TransportSocketMatches, &clusterv3.Cluster_TransportSocketMatch{ - Name: matchName, - Match: &structpb.Struct{ - Fields: map[string]*structpb.Value{ - "name": structpb.NewStringValue(matchName), + + // Dynamic resolver clusters have no endpoints, so we need to set the transport socket directly. + if args.endpointType == EndpointTypeDynamicResolver { + cluster.TransportSocket = socket + } else { + cluster.TransportSocketMatches = append(cluster.TransportSocketMatches, &clusterv3.Cluster_TransportSocketMatch{ + Name: matchName, + Match: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "name": structpb.NewStringValue(matchName), + }, }, - }, - TransportSocket: socket, - }) + TransportSocket: socket, + }) + } } } diff --git a/internal/xds/translator/testdata/in/xds-ir/http-route-dynamic-resolver.yaml b/internal/xds/translator/testdata/in/xds-ir/http-route-dynamic-resolver.yaml index 5d7939120e..11c3aa8fb9 100644 --- a/internal/xds/translator/testdata/in/xds-ir/http-route-dynamic-resolver.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/http-route-dynamic-resolver.yaml @@ -19,6 +19,11 @@ http: settings: - isDynamicResolver: true name: httproute/default/httproute-1/rule/0/backend/0 + tls: + alpnProtocols: null + caCertificate: + certificate: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURKekNDQWcrZ0F3SUJBZ0lVQWw2VUtJdUttenRlODFjbGx6NVBmZE4ySWxJd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0l6RVFNQTRHQTFVRUF3d0hiWGxqYVdWdWRERVBNQTBHQTFVRUNnd0dhM1ZpWldSaU1CNFhEVEl6TVRBdwpNakExTkRFMU4xb1hEVEkwTVRBd01UQTFOREUxTjFvd0l6RVFNQTRHQTFVRUF3d0hiWGxqYVdWdWRERVBNQTBHCkExVUVDZ3dHYTNWaVpXUmlNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdTVGMKMXlqOEhXNjJueW5rRmJYbzRWWEt2MmpDMFBNN2RQVmt5ODdGd2VaY1RLTG9XUVZQUUUycDJrTERLNk9Fc3ptTQp5eXIreHhXdHlpdmVyZW1yV3FuS2tOVFloTGZZUGhnUWtjemliN2VVYWxtRmpVYmhXZEx2SGFrYkVnQ29kbjNiCmt6NTdtSW5YMlZwaURPS2c0a3lIZml1WFdwaUJxckN4MEtOTHB4bzNERVFjRmNzUVRlVEh6aDQ3NTJHVjA0UlUKVGkvR0VXeXpJc2w0Umc3dEd0QXdtY0lQZ1VOVWZZMlEzOTBGR3FkSDRhaG4rbXcvNmFGYlczMVc2M2Q5WUpWcQppb3lPVmNhTUlwTTVCL2M3UWM4U3VoQ0kxWUdoVXlnNGNSSExFdzVWdGlraW95RTNYMDRrbmEzalFBajU0WWJSCmJwRWhjMzVhcEtMQjIxSE9VUUlEQVFBQm8xTXdVVEFkQmdOVkhRNEVGZ1FVeXZsMFZJNXZKVlN1WUZYdTdCNDgKNlBiTUVBb3dId1lEVlIwakJCZ3dGb0FVeXZsMFZJNXZKVlN1WUZYdTdCNDg2UGJNRUFvd0R3WURWUjBUQVFILwpCQVV3QXdFQi96QU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFNTHhyZ0ZWTXVOUnEyd0F3Y0J0N1NuTlI1Q2Z6CjJNdlhxNUVVbXVhd0lVaTlrYVlqd2RWaURSRUdTams3SlcxN3ZsNTc2SGpEa2RmUndpNEUyOFN5ZFJJblpmNkoKaThIWmNaN2NhSDZEeFIzMzVmZ0hWekxpNU5pVGNlL09qTkJRelEyTUpYVkRkOERCbUc1ZnlhdEppT0pRNGJXRQpBN0ZsUDBSZFAzQ08zR1dFME01aVhPQjJtMXFXa0UyZXlPNFVIdndUcU5RTGRyZEFYZ0RRbGJhbTllNEJHM0dnCmQvNnRoQWtXRGJ0L1FOVCtFSkhEQ3ZoRFJLaDFSdUdIeWcrWSsvbmViVFdXckZXc2t0UnJiT29IQ1ppQ3BYSTEKM2VYRTZudDBZa2d0RHhHMjJLcW5ocEFnOWdVU3MyaGxob3h5dmt6eUYwbXU2TmhQbHdBZ25xNysvUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + name: backend-1/default-ca weight: 1 hostname: gateway.envoyproxy.io isHTTP2: false diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.clusters.yaml index 29b8be2f44..818cf07425 100644 --- a/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.clusters.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.clusters.yaml @@ -16,3 +16,15 @@ lbPolicy: CLUSTER_PROVIDED name: httproute/default/httproute-1/rule/0 perConnectionBufferLimitBytes: 32768 + transportSocket: + name: envoy.transport_sockets.tls + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + commonTlsContext: + combinedValidationContext: + defaultValidationContext: {} + validationContextSdsSecretConfig: + name: backend-1/default-ca + sdsConfig: + ads: {} + resourceApiVersion: V3 diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.secrets.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.secrets.yaml new file mode 100644 index 0000000000..3a9ed37085 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/http-route-dynamic-resolver.secrets.yaml @@ -0,0 +1,4 @@ +- name: backend-1/default-ca + validationContext: + trustedCa: + inlineBytes: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURKekNDQWcrZ0F3SUJBZ0lVQWw2VUtJdUttenRlODFjbGx6NVBmZE4ySWxJd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0l6RVFNQTRHQTFVRUF3d0hiWGxqYVdWdWRERVBNQTBHQTFVRUNnd0dhM1ZpWldSaU1CNFhEVEl6TVRBdwpNakExTkRFMU4xb1hEVEkwTVRBd01UQTFOREUxTjFvd0l6RVFNQTRHQTFVRUF3d0hiWGxqYVdWdWRERVBNQTBHCkExVUVDZ3dHYTNWaVpXUmlNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXdTVGMKMXlqOEhXNjJueW5rRmJYbzRWWEt2MmpDMFBNN2RQVmt5ODdGd2VaY1RLTG9XUVZQUUUycDJrTERLNk9Fc3ptTQp5eXIreHhXdHlpdmVyZW1yV3FuS2tOVFloTGZZUGhnUWtjemliN2VVYWxtRmpVYmhXZEx2SGFrYkVnQ29kbjNiCmt6NTdtSW5YMlZwaURPS2c0a3lIZml1WFdwaUJxckN4MEtOTHB4bzNERVFjRmNzUVRlVEh6aDQ3NTJHVjA0UlUKVGkvR0VXeXpJc2w0Umc3dEd0QXdtY0lQZ1VOVWZZMlEzOTBGR3FkSDRhaG4rbXcvNmFGYlczMVc2M2Q5WUpWcQppb3lPVmNhTUlwTTVCL2M3UWM4U3VoQ0kxWUdoVXlnNGNSSExFdzVWdGlraW95RTNYMDRrbmEzalFBajU0WWJSCmJwRWhjMzVhcEtMQjIxSE9VUUlEQVFBQm8xTXdVVEFkQmdOVkhRNEVGZ1FVeXZsMFZJNXZKVlN1WUZYdTdCNDgKNlBiTUVBb3dId1lEVlIwakJCZ3dGb0FVeXZsMFZJNXZKVlN1WUZYdTdCNDg2UGJNRUFvd0R3WURWUjBUQVFILwpCQVV3QXdFQi96QU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFNTHhyZ0ZWTXVOUnEyd0F3Y0J0N1NuTlI1Q2Z6CjJNdlhxNUVVbXVhd0lVaTlrYVlqd2RWaURSRUdTams3SlcxN3ZsNTc2SGpEa2RmUndpNEUyOFN5ZFJJblpmNkoKaThIWmNaN2NhSDZEeFIzMzVmZ0hWekxpNU5pVGNlL09qTkJRelEyTUpYVkRkOERCbUc1ZnlhdEppT0pRNGJXRQpBN0ZsUDBSZFAzQ08zR1dFME01aVhPQjJtMXFXa0UyZXlPNFVIdndUcU5RTGRyZEFYZ0RRbGJhbTllNEJHM0dnCmQvNnRoQWtXRGJ0L1FOVCtFSkhEQ3ZoRFJLaDFSdUdIeWcrWSsvbmViVFdXckZXc2t0UnJiT29IQ1ppQ3BYSTEKM2VYRTZudDBZa2d0RHhHMjJLcW5ocEFnOWdVU3MyaGxob3h5dmt6eUYwbXU2TmhQbHdBZ25xNysvUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K diff --git a/internal/xds/translator/translator.go b/internal/xds/translator/translator.go index aadde3f428..5118ffd329 100644 --- a/internal/xds/translator/translator.go +++ b/internal/xds/translator/translator.go @@ -1024,31 +1024,38 @@ func buildXdsUpstreamTLSCASecret(tlsConfig *ir.TLSUpstreamConfig) *tlsv3.Secret } func buildXdsUpstreamTLSSocketWthCert(tlsConfig *ir.TLSUpstreamConfig) (*corev3.TransportSocket, error) { + validationContext := &tlsv3.CommonTlsContext_CombinedCertificateValidationContext{ + ValidationContextSdsSecretConfig: &tlsv3.SdsSecretConfig{ + Name: tlsConfig.CACertificate.Name, + SdsConfig: makeConfigSource(), + }, + DefaultValidationContext: &tlsv3.CertificateValidationContext{}, + } + + if tlsConfig.SNI != nil { + validationContext.DefaultValidationContext.MatchTypedSubjectAltNames = []*tlsv3.SubjectAltNameMatcher{ + { + SanType: tlsv3.SubjectAltNameMatcher_DNS, + Matcher: &matcherv3.StringMatcher{ + MatchPattern: &matcherv3.StringMatcher_Exact{ + Exact: *tlsConfig.SNI, + }, + }, + }, + } + } + tlsCtx := &tlsv3.UpstreamTlsContext{ CommonTlsContext: &tlsv3.CommonTlsContext{ TlsCertificateSdsSecretConfigs: nil, ValidationContextType: &tlsv3.CommonTlsContext_CombinedValidationContext{ - CombinedValidationContext: &tlsv3.CommonTlsContext_CombinedCertificateValidationContext{ - ValidationContextSdsSecretConfig: &tlsv3.SdsSecretConfig{ - Name: tlsConfig.CACertificate.Name, - SdsConfig: makeConfigSource(), - }, - DefaultValidationContext: &tlsv3.CertificateValidationContext{ - MatchTypedSubjectAltNames: []*tlsv3.SubjectAltNameMatcher{ - { - SanType: tlsv3.SubjectAltNameMatcher_DNS, - Matcher: &matcherv3.StringMatcher{ - MatchPattern: &matcherv3.StringMatcher_Exact{ - Exact: tlsConfig.SNI, - }, - }, - }, - }, - }, - }, + CombinedValidationContext: validationContext, }, }, - Sni: tlsConfig.SNI, + } + + if tlsConfig.SNI != nil { + tlsCtx.Sni = *tlsConfig.SNI } tlsParams := buildTLSParams(&tlsConfig.TLSConfig) diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 2c7a476643..5997d650b3 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -405,6 +405,7 @@ _Appears in:_ | `endpoints` | _[BackendEndpoint](#backendendpoint) array_ | true | | Endpoints defines the endpoints to be used when connecting to the backend. | | `appProtocols` | _[AppProtocolType](#appprotocoltype) array_ | false | | AppProtocols defines the application protocols to be supported when connecting to the backend. | | `fallback` | _boolean_ | false | | Fallback indicates whether the backend is designated as a fallback.
It is highly recommended to configure active or passive health checks to ensure that failover can be detected
when the active backends become unhealthy and to automatically readjust once the primary backends are healthy again.
The overprovisioning factor is set to 1.4, meaning the fallback backends will only start receiving traffic when
the health of the active backends falls below 72%. | +| `tls` | _[BackendTLSSettings](#backendtlssettings)_ | false | | TLS defines the TLS settings for the backend.
Only supported for DynamicResolver backends. | #### BackendStatus diff --git a/test/cel-validation/backend_test.go b/test/cel-validation/backend_test.go index b02bb4a8bb..a29c36169d 100644 --- a/test/cel-validation/backend_test.go +++ b/test/cel-validation/backend_test.go @@ -16,6 +16,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" + gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" ) @@ -274,6 +275,30 @@ func TestBackend(t *testing.T) { }, wantErrors: []string{"DynamicResolver type cannot have endpoints and appProtocols specified"}, }, + { + desc: "tls settings on non-dynamic resolver", + mutate: func(backend *egv1a1.Backend) { + backend.Spec = egv1a1.BackendSpec{ + AppProtocols: []egv1a1.AppProtocolType{egv1a1.AppProtocolTypeH2C}, + Endpoints: []egv1a1.BackendEndpoint{ + { + FQDN: &egv1a1.FQDNEndpoint{ + Hostname: "example.com", + Port: 443, + }, + }, + }, + TLS: &egv1a1.BackendTLSSettings{ + CACertificateRefs: []gwapiv1.LocalObjectReference{ + { + Name: "ca-certificate", + }, + }, + }, + } + }, + wantErrors: []string{"TLS settings can only be specified for DynamicResolver backends"}, + }, } for _, tc := range cases { diff --git a/test/e2e/testdata/httproute-with-dynamic-resolver-backend-with-tls.yaml b/test/e2e/testdata/httproute-with-dynamic-resolver-backend-with-tls.yaml new file mode 100644 index 0000000000..8f622b5570 --- /dev/null +++ b/test/e2e/testdata/httproute-with-dynamic-resolver-backend-with-tls.yaml @@ -0,0 +1,151 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: httproute-with-dynamic-resolver-backend-tls + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + rules: + - backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend-dynamic-resolver-tls + matches: + - path: + type: PathPrefix + value: /with-tls +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: Backend +metadata: + name: backend-dynamic-resolver-tls + namespace: gateway-conformance-infra +spec: + type: DynamicResolver + tls: + caCertificateRefs: + - name: backend-ca-certificate + group: "" + kind: ConfigMap +--- +# req -x509 -sha256 -nodes -days 36500 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout ca.key -out ca.crt +apiVersion: v1 +kind: ConfigMap +metadata: + name: backend-ca-certificate + namespace: gateway-conformance-infra +data: + ca.crt: | + -----BEGIN CERTIFICATE----- + MIIDPTCCAiWgAwIBAgIUcpXGG2fJigPwvq46BxmVmgxlGxQwDQYJKoZIhvcNAQEL + BQAwLTEVMBMGA1UECgwMZXhhbXBsZSBJbmMuMRQwEgYDVQQDDAtleGFtcGxlLmNv + bTAgFw0yNTA1MDIwNzQwMTdaGA8yMTI1MDQwODA3NDAxN1owLTEVMBMGA1UECgwM + ZXhhbXBsZSBJbmMuMRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN + AQEBBQADggEPADCCAQoCggEBAKJufjXwrd+NYesqHU96eWDDTdTHzPTwIw7u3PVd + +lMY+aKHS4yJNOO9uA8mHilFlfXJOqBj8JU4zgWRrUWKmdd4sQuuoQczZzTScyjc + OKk/uIhvp0n5HTjR2q1gaCYS223fJ90e2WChZeokPXkTcKTAQm/5aizGQ5rzlZSk + CNTAgdKK80UXDkOHxx3gkmodGhcRW4BsmXEyCm1jkUYrkIQm9K9cZQKeeyIDbTA8 + Y3Y9v5dubbrLOmEOeEIN2uJpru706zhTcCj142hlDwgh1I4z9et5BcdN0Mi2eQDs + R0wwE5/UKmQpA9i9rsurSN+vzKlh3v+YWjv/493AWOs/VFUCAwEAAaNTMFEwHQYD + VR0OBBYEFG6yHRlAqWAIyCj9YmiViR1z9vgnMB8GA1UdIwQYMBaAFG6yHRlAqWAI + yCj9YmiViR1z9vgnMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB + AHe6SKBHOK9Fv0UzxaX98JhI6kOr1pR08RAR/pr1t2oeHm0tdWOvdrukRAq9AOng + OVBQBcMKz+PRQQ6dA2Gzh3UE5lorQ+jkbon5DY6oTZnMCPo5b3Dy/sN6hl3/w2Yi + uTv8iMcU5VloeDRqUbKTtNX3o6LymkIJ+S3tLutQr8xzNt6SnJAnZ4PcgrZY5Xlq + Othq6aXaCOfvH/yoQoSIxVMhl1QbcI8EgjY7z9523nKlOvYc36Qe6z7zqqLvVfk7 + iFlA2NThEl+N/7SOn37++sOhFvA85X7WQ92jWi2Cz5s1Z6AZRoWz703+GxyzEyk3 + gFEPoFIv6ijfvPhUytPUBr4= + -----END CERTIFICATE----- +--- +# cat > openssl.conf < 0 && has(self.wellKnownCACertificates) && self.wellKnownCACertificates + != "")' + - message: must specify either CACertificateRefs or WellKnownCACertificates + rule: (has(self.caCertificateRefs) && size(self.caCertificateRefs) + > 0 || has(self.wellKnownCACertificates) && self.wellKnownCACertificates + != "") type: default: Endpoints description: Type defines the type of the backend. Defaults to "Endpoints" @@ -17537,6 +17546,8 @@ spec: - message: DynamicResolver type cannot have endpoints and appProtocols specified rule: self.type != 'DynamicResolver' || !has(self.endpoints) && !has(self.appProtocols) + - message: TLS settings can only be specified for DynamicResolver backends + rule: 'has(self.tls) ? self.type == ''DynamicResolver'' : true' status: description: Status defines the current status of Backend. properties: diff --git a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml index c478588002..086ac96c89 100644 --- a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml +++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml @@ -213,6 +213,15 @@ spec: - System type: string type: object + x-kubernetes-validations: + - message: must not contain both CACertificateRefs and WellKnownCACertificates + rule: '!(has(self.caCertificateRefs) && size(self.caCertificateRefs) + > 0 && has(self.wellKnownCACertificates) && self.wellKnownCACertificates + != "")' + - message: must specify either CACertificateRefs or WellKnownCACertificates + rule: (has(self.caCertificateRefs) && size(self.caCertificateRefs) + > 0 || has(self.wellKnownCACertificates) && self.wellKnownCACertificates + != "") type: default: Endpoints description: Type defines the type of the backend. Defaults to "Endpoints" @@ -225,6 +234,8 @@ spec: - message: DynamicResolver type cannot have endpoints and appProtocols specified rule: self.type != 'DynamicResolver' || !has(self.endpoints) && !has(self.appProtocols) + - message: TLS settings can only be specified for DynamicResolver backends + rule: 'has(self.tls) ? self.type == ''DynamicResolver'' : true' status: description: Status defines the current status of Backend. properties: From 48b85d6505f49e84337411cac8ab1a74f997ec32 Mon Sep 17 00:00:00 2001 From: Isaac <10012479+jukie@users.noreply.github.com> Date: Mon, 5 May 2025 22:16:50 -0600 Subject: [PATCH 20/66] fix: fix topology injector bug (#5911) * fix webhook Signed-off-by: Jukie <10012479+Jukie@users.noreply.github.com> * lint and test fixes Signed-off-by: Jukie <10012479+Jukie@users.noreply.github.com> --------- Signed-off-by: Jukie <10012479+Jukie@users.noreply.github.com> Signed-off-by: Arko Dasgupta --- go.mod | 4 +- internal/cmd/certgen.go | 17 +++-- internal/cmd/certgen_test.go | 19 ++++-- .../kubernetes/proxy/resource.go | 2 +- .../testdata/daemonsets/component-level.yaml | 4 +- .../proxy/testdata/daemonsets/custom.yaml | 4 +- .../testdata/daemonsets/default-env.yaml | 4 +- .../proxy/testdata/daemonsets/default.yaml | 4 +- .../daemonsets/disable-prometheus.yaml | 4 +- .../testdata/daemonsets/extension-env.yaml | 4 +- .../override-labels-and-annotations.yaml | 4 +- .../testdata/daemonsets/patch-daemonset.yaml | 4 +- .../testdata/daemonsets/shutdown-manager.yaml | 4 +- .../proxy/testdata/daemonsets/volumes.yaml | 4 +- .../testdata/daemonsets/with-annotations.yaml | 4 +- .../testdata/daemonsets/with-concurrency.yaml | 4 +- .../testdata/daemonsets/with-extra-args.yaml | 4 +- .../daemonsets/with-image-pull-secrets.yaml | 4 +- .../proxy/testdata/daemonsets/with-name.yaml | 4 +- .../daemonsets/with-node-selector.yaml | 4 +- .../with-topology-spread-constraints.yaml | 4 +- .../proxy/testdata/deployments/bootstrap.yaml | 4 +- .../testdata/deployments/component-level.yaml | 4 +- .../proxy/testdata/deployments/custom.yaml | 4 +- .../custom_with_initcontainers.yaml | 4 +- .../testdata/deployments/default-env.yaml | 4 +- .../proxy/testdata/deployments/default.yaml | 4 +- .../deployments/disable-prometheus.yaml | 4 +- .../testdata/deployments/dual-stack.yaml | 4 +- .../testdata/deployments/extension-env.yaml | 4 +- .../proxy/testdata/deployments/ipv6.yaml | 4 +- .../override-labels-and-annotations.yaml | 4 +- .../deployments/patch-deployment.yaml | 4 +- .../deployments/shutdown-manager.yaml | 4 +- .../proxy/testdata/deployments/volumes.yaml | 4 +- .../deployments/with-annotations.yaml | 4 +- .../deployments/with-concurrency.yaml | 4 +- .../deployments/with-empty-memory-limits.yaml | 4 +- .../testdata/deployments/with-extra-args.yaml | 4 +- .../deployments/with-image-pull-secrets.yaml | 4 +- .../proxy/testdata/deployments/with-name.yaml | 4 +- .../deployments/with-node-selector.yaml | 4 +- .../with-topology-spread-constraints.yaml | 4 +- .../provider/kubernetes/topology_injector.go | 24 +++---- .../kubernetes/topology_injector_test.go | 63 ++++++++----------- 45 files changed, 147 insertions(+), 138 deletions(-) diff --git a/go.mod b/go.mod index 2b551d0329..03803c6c91 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,6 @@ require ( github.com/go-logfmt/logfmt v0.6.0 github.com/go-logr/logr v1.4.2 github.com/go-logr/zapr v1.3.0 - github.com/go-openapi/jsonpointer v0.21.1 github.com/go-openapi/spec v0.21.0 github.com/go-openapi/strfmt v0.23.0 github.com/go-openapi/validate v0.24.0 @@ -62,6 +61,7 @@ require ( go.uber.org/zap v1.27.0 golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 golang.org/x/net v0.39.0 + gomodules.xyz/jsonpatch/v2 v2.4.0 google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a google.golang.org/grpc v1.72.0 google.golang.org/grpc/security/advancedtls v1.0.0 @@ -221,6 +221,7 @@ require ( github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-openapi/analysis v0.23.0 // indirect github.com/go-openapi/errors v0.22.0 // indirect + github.com/go-openapi/jsonpointer v0.21.1 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/loads v0.22.0 // indirect github.com/go-openapi/swag v0.23.1 // indirect @@ -489,7 +490,6 @@ require ( golang.org/x/time v0.10.0 // indirect golang.org/x/tools v0.31.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 // indirect google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 // indirect gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect diff --git a/internal/cmd/certgen.go b/internal/cmd/certgen.go index 99950f5cf0..79b11f7dea 100644 --- a/internal/cmd/certgen.go +++ b/internal/cmd/certgen.go @@ -15,6 +15,8 @@ import ( "github.com/spf13/cobra" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" clicfg "sigs.k8s.io/controller-runtime/pkg/client/config" @@ -80,7 +82,7 @@ func certGen(ctx context.Context, logOut io.Writer, local bool) error { if err = outputCertsForKubernetes(ctx, cli, cfg, overwriteControlPlaneCerts, certs); err != nil { return fmt.Errorf("failed to output certificates: %w", err) } - if err = patchTopologyInjectorWebhook(ctx, cli, cfg, certs.CACertificate); err != nil { + if err = patchTopologyInjectorWebhook(ctx, cli, cfg); err != nil { return fmt.Errorf("failed to patch webhook: %w", err) } } else { @@ -116,7 +118,7 @@ func outputCertsForKubernetes(ctx context.Context, cli client.Client, cfg *confi return nil } -func patchTopologyInjectorWebhook(ctx context.Context, cli client.Client, cfg *config.Server, caBundle []byte) error { +func patchTopologyInjectorWebhook(ctx context.Context, cli client.Client, cfg *config.Server) error { if disableTopologyInjector { return nil } @@ -127,10 +129,17 @@ func patchTopologyInjectorWebhook(ctx context.Context, cli client.Client, cfg *c return fmt.Errorf("failed to get mutating webhook configuration: %w", err) } + secretName := types.NamespacedName{Name: "envoy-gateway", Namespace: cfg.ControllerNamespace} + current := &corev1.Secret{} + if err := cli.Get(ctx, secretName, current); err != nil { + return fmt.Errorf("failed to get secret %s/%s: %w", current.Namespace, current.Name, err) + } + var updated bool + desiredBundle := current.Data["ca.crt"] for i, webhook := range webhookCfg.Webhooks { - if !bytes.Equal(caBundle, webhook.ClientConfig.CABundle) { - webhookCfg.Webhooks[i].ClientConfig.CABundle = caBundle + if !bytes.Equal(desiredBundle, webhook.ClientConfig.CABundle) { + webhookCfg.Webhooks[i].ClientConfig.CABundle = desiredBundle updated = true } } diff --git a/internal/cmd/certgen_test.go b/internal/cmd/certgen_test.go index 27e7791310..b360a19bbe 100644 --- a/internal/cmd/certgen_test.go +++ b/internal/cmd/certgen_test.go @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" @@ -57,7 +58,7 @@ func TestPatchTopologyWebhook(t *testing.T) { cases := []struct { caseName string webhook *admissionregistrationv1.MutatingWebhookConfiguration - caBundle []byte + secret *corev1.Secret wantErr error wantPatch bool }{ @@ -69,7 +70,10 @@ func TestPatchTopologyWebhook(t *testing.T) { }, Webhooks: []admissionregistrationv1.MutatingWebhook{{ClientConfig: admissionregistrationv1.WebhookClientConfig{}}}, }, - caBundle: []byte("foo"), + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "envoy-gateway", Namespace: cfg.ControllerNamespace}, + Data: map[string][]byte{"ca.crt": []byte("foo")}, + }, wantErr: nil, wantPatch: true, }, @@ -81,25 +85,28 @@ func TestPatchTopologyWebhook(t *testing.T) { }, Webhooks: []admissionregistrationv1.MutatingWebhook{{ClientConfig: admissionregistrationv1.WebhookClientConfig{CABundle: []byte("foo")}}}, }, - caBundle: []byte("foo"), + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "envoy-gateway", Namespace: cfg.ControllerNamespace}, + Data: map[string][]byte{"ca.crt": []byte("foo")}, + }, wantPatch: false, }, } for _, tc := range cases { t.Run(tc.caseName, func(t *testing.T) { fakeClient := fake.NewClientBuilder(). - WithRuntimeObjects(tc.webhook). + WithRuntimeObjects(tc.webhook, tc.secret). Build() beforeWebhook := &admissionregistrationv1.MutatingWebhookConfiguration{} require.NoError(t, fakeClient.Get(context.Background(), client.ObjectKey{Name: tc.webhook.Name}, beforeWebhook)) - err = patchTopologyInjectorWebhook(context.Background(), fakeClient, cfg, tc.caBundle) + err = patchTopologyInjectorWebhook(context.Background(), fakeClient, cfg) require.NoError(t, err) afterWebhook := &admissionregistrationv1.MutatingWebhookConfiguration{} require.NoError(t, fakeClient.Get(context.Background(), client.ObjectKey{Name: tc.webhook.Name}, afterWebhook)) - require.Equal(t, afterWebhook.Webhooks[0].ClientConfig.CABundle, tc.caBundle) + require.Equal(t, afterWebhook.Webhooks[0].ClientConfig.CABundle, tc.secret.Data["ca.crt"]) assert.Equal(t, tc.wantPatch, beforeWebhook.GetResourceVersion() != afterWebhook.GetResourceVersion()) }) } diff --git a/internal/infrastructure/kubernetes/proxy/resource.go b/internal/infrastructure/kubernetes/proxy/resource.go index d98e68cf4d..6dba6e525f 100644 --- a/internal/infrastructure/kubernetes/proxy/resource.go +++ b/internal/infrastructure/kubernetes/proxy/resource.go @@ -402,7 +402,7 @@ func expectedContainerEnv(containerSpec *egv1a1.KubernetesContainerSpec, gateway ValueFrom: &corev1.EnvVarSource{ FieldRef: &corev1.ObjectFieldSelector{ APIVersion: "v1", - FieldPath: fmt.Sprintf("metadata.labels['%s']", corev1.LabelTopologyZone), + FieldPath: fmt.Sprintf("metadata.annotations['%s']", corev1.LabelTopologyZone), }, }, }, diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/component-level.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/component-level.yaml index 29d630056d..ae7b709f6d 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/component-level.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/component-level.yaml @@ -55,7 +55,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -142,7 +142,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/custom.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/custom.yaml index 782cda71d5..46a6b4dbed 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/custom.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/custom.yaml @@ -258,7 +258,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -339,7 +339,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default-env.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default-env.yaml index e5ac89e0c6..c8b7570501 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default-env.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default-env.yaml @@ -257,7 +257,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -338,7 +338,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default.yaml index cb1f9fb4ab..ed33f0d6db 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default.yaml @@ -242,7 +242,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -329,7 +329,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/disable-prometheus.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/disable-prometheus.yaml index ed86762b7f..ea4a554ba4 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/disable-prometheus.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/disable-prometheus.yaml @@ -191,7 +191,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -275,7 +275,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/extension-env.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/extension-env.yaml index 76ed4ea1e9..344b3d3229 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/extension-env.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/extension-env.yaml @@ -257,7 +257,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -342,7 +342,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml index d2fc222098..ed44da4123 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml @@ -251,7 +251,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -338,7 +338,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/patch-daemonset.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/patch-daemonset.yaml index 086d41bc0c..b0ecbe7bea 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/patch-daemonset.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/patch-daemonset.yaml @@ -242,7 +242,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -329,7 +329,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/shutdown-manager.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/shutdown-manager.yaml index 41c0ffe943..0d6539199c 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/shutdown-manager.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/shutdown-manager.yaml @@ -242,7 +242,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -334,7 +334,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/volumes.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/volumes.yaml index 5573bbfef2..eb22669380 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/volumes.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/volumes.yaml @@ -257,7 +257,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -342,7 +342,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-annotations.yaml index 95324ff5cf..846cf09502 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-annotations.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-annotations.yaml @@ -247,7 +247,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -334,7 +334,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-concurrency.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-concurrency.yaml index 522e982f7f..6d526e1500 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-concurrency.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-concurrency.yaml @@ -55,7 +55,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -142,7 +142,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-extra-args.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-extra-args.yaml index a3ec10df3d..52a4bc724f 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-extra-args.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-extra-args.yaml @@ -244,7 +244,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -331,7 +331,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-image-pull-secrets.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-image-pull-secrets.yaml index c3c4f6207a..6b8aea93a0 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-image-pull-secrets.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-image-pull-secrets.yaml @@ -242,7 +242,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -329,7 +329,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-name.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-name.yaml index 5f89555e9f..94cb3d5bf9 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-name.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-name.yaml @@ -242,7 +242,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -329,7 +329,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-node-selector.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-node-selector.yaml index 899bb57bcc..5dd4d9d20b 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-node-selector.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-node-selector.yaml @@ -242,7 +242,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -329,7 +329,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-topology-spread-constraints.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-topology-spread-constraints.yaml index fc340f6c79..799cd00c8b 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-topology-spread-constraints.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-topology-spread-constraints.yaml @@ -242,7 +242,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -329,7 +329,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/bootstrap.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/bootstrap.yaml index 9448ef3356..62f20843ac 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/bootstrap.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/bootstrap.yaml @@ -58,7 +58,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -145,7 +145,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/component-level.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/component-level.yaml index 9f9d54a022..c80dd9a4fa 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/component-level.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/component-level.yaml @@ -59,7 +59,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -146,7 +146,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom.yaml index 8b4f231166..60676d3903 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom.yaml @@ -263,7 +263,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -344,7 +344,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom_with_initcontainers.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom_with_initcontainers.yaml index 8f95d6668a..ed1b655c87 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom_with_initcontainers.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom_with_initcontainers.yaml @@ -263,7 +263,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -346,7 +346,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/default-env.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/default-env.yaml index ae2a8c316a..fa2b709e16 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/default-env.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/default-env.yaml @@ -262,7 +262,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -343,7 +343,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/default.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/default.yaml index ef20a2299c..29b60d073c 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/default.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/default.yaml @@ -246,7 +246,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -333,7 +333,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/disable-prometheus.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/disable-prometheus.yaml index d1ad61f94e..94fda682b3 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/disable-prometheus.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/disable-prometheus.yaml @@ -195,7 +195,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -279,7 +279,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/dual-stack.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/dual-stack.yaml index 6db4107d7d..7a850e68c8 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/dual-stack.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/dual-stack.yaml @@ -247,7 +247,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -334,7 +334,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/extension-env.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/extension-env.yaml index 1aab161d96..cc8e5d1c6c 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/extension-env.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/extension-env.yaml @@ -262,7 +262,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -347,7 +347,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/ipv6.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/ipv6.yaml index 454b05778a..cf1932a2d6 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/ipv6.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/ipv6.yaml @@ -247,7 +247,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -334,7 +334,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml index 0f2da87535..8c32258bdc 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml @@ -255,7 +255,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -342,7 +342,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/patch-deployment.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/patch-deployment.yaml index f01683108c..d058555584 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/patch-deployment.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/patch-deployment.yaml @@ -246,7 +246,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -333,7 +333,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/shutdown-manager.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/shutdown-manager.yaml index 1f3b5d0d17..e231ba2bb2 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/shutdown-manager.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/shutdown-manager.yaml @@ -246,7 +246,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -338,7 +338,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/volumes.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/volumes.yaml index 212663a71b..896a680709 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/volumes.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/volumes.yaml @@ -262,7 +262,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -347,7 +347,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-annotations.yaml index f04390c6f8..0c57f4c82b 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-annotations.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-annotations.yaml @@ -251,7 +251,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -338,7 +338,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-concurrency.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-concurrency.yaml index 0371a5c1fc..2c68a3bbfd 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-concurrency.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-concurrency.yaml @@ -59,7 +59,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -146,7 +146,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-empty-memory-limits.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-empty-memory-limits.yaml index 7a0575d32b..808e331771 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-empty-memory-limits.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-empty-memory-limits.yaml @@ -246,7 +246,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -332,7 +332,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-extra-args.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-extra-args.yaml index 086bee076f..bde6f343dc 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-extra-args.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-extra-args.yaml @@ -248,7 +248,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -335,7 +335,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-image-pull-secrets.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-image-pull-secrets.yaml index e1b26bfe01..2dd70ff619 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-image-pull-secrets.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-image-pull-secrets.yaml @@ -246,7 +246,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -333,7 +333,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-name.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-name.yaml index 71705889d6..aabd4640fa 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-name.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-name.yaml @@ -246,7 +246,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -333,7 +333,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-node-selector.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-node-selector.yaml index 799ca61af9..9aaedf43c9 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-node-selector.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-node-selector.yaml @@ -246,7 +246,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -333,7 +333,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-topology-spread-constraints.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-topology-spread-constraints.yaml index cb5fafb3f2..6a18de2db9 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-topology-spread-constraints.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-topology-spread-constraints.yaml @@ -246,7 +246,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -333,7 +333,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/provider/kubernetes/topology_injector.go b/internal/provider/kubernetes/topology_injector.go index d53ba82249..f507c72989 100644 --- a/internal/provider/kubernetes/topology_injector.go +++ b/internal/provider/kubernetes/topology_injector.go @@ -7,9 +7,9 @@ package kubernetes import ( "context" + "encoding/json" "fmt" - "github.com/go-openapi/jsonpointer" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/klog/v2" @@ -63,24 +63,26 @@ func (m *ProxyTopologyInjector) Handle(ctx context.Context, req admission.Reques node := &corev1.Node{} if err := m.Get(ctx, nodeName, node); err != nil { klog.Error(err, "get node failed", "node", node.Name) + topologyInjectorEventsTotal.WithFailure(metrics.ReasonError).Increment() return admission.Allowed("internal error, skipped") } - var patch string if zone, ok := node.Labels[corev1.LabelTopologyZone]; ok { - patch = fmt.Sprintf(`[{"op":"replace", "path":"/metadata/labels/%s", "value":"%s"}]`, jsonpointer.Escape(corev1.LabelTopologyZone), zone) + if binding.Annotations == nil { + binding.Annotations = map[string]string{} + } + binding.Annotations[corev1.LabelTopologyZone] = zone + } else { + return admission.Allowed("Skipping injection due to missing topology label on node") } - rawPatch := client.RawPatch(types.JSONPatchType, []byte(patch)) - if err := m.Patch(ctx, pod, rawPatch); err != nil { - klog.Error(err, "patch pod failed", "pod", podName.String()) - topologyInjectorEventsTotal.WithFailure(metrics.ReasonError).Increment() - return admission.Allowed("internal error, skipped") + marshaledBinding, err := json.Marshal(binding) + if err != nil { + klog.Errorf("failed to marshal Pod Binding: %v", err) + return admission.Allowed(fmt.Sprintf("failed to marshal binding, skipped: %v", err)) } - klog.V(1).Info("patch pod succeeded", "pod", podName.String()) - topologyInjectorEventsTotal.WithSuccess().Increment() - return admission.Allowed("pod patched") + return admission.PatchResponseFromRaw(req.Object.Raw, marshaledBinding) } func hasEnvoyProxyLabels(labels map[string]string) bool { diff --git a/internal/provider/kubernetes/topology_injector_test.go b/internal/provider/kubernetes/topology_injector_test.go index 726680854c..6128214a80 100644 --- a/internal/provider/kubernetes/topology_injector_test.go +++ b/internal/provider/kubernetes/topology_injector_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/stretchr/testify/require" + "gomodules.xyz/jsonpatch/v2" admissionv1 "k8s.io/api/admission/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -45,11 +46,11 @@ func TestProxyTopologyInjector_Handle(t *testing.T) { } cases := []struct { - caseName string - obj client.Object - node *corev1.Node - pod *corev1.Pod - wantErr bool + caseName string + obj client.Object + node *corev1.Node + pod *corev1.Pod + expectedPatchResp []jsonpatch.JsonPatchOperation }{ { caseName: "valid binding", @@ -60,9 +61,15 @@ func TestProxyTopologyInjector_Handle(t *testing.T) { }, Target: corev1.ObjectReference{Name: defaultNode.Name}, }, - node: defaultNode, - pod: defaultPod, - wantErr: false, + node: defaultNode, + pod: defaultPod, + expectedPatchResp: []jsonpatch.JsonPatchOperation{{ + Operation: "add", + Path: "/metadata/annotations", + Value: map[string]interface{}{ + "topology.kubernetes.io/zone": "zone1", + }, + }}, }, { caseName: "empty target", @@ -72,9 +79,9 @@ func TestProxyTopologyInjector_Handle(t *testing.T) { Namespace: defaultPod.Namespace, }, }, - node: defaultNode, - pod: defaultPod, - wantErr: true, + node: defaultNode, + pod: defaultPod, + expectedPatchResp: nil, }, { caseName: "skip binding - no label", @@ -84,9 +91,9 @@ func TestProxyTopologyInjector_Handle(t *testing.T) { Namespace: "bar", }, }, - node: defaultNode, - pod: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "bar", Name: "baz"}}, - wantErr: true, + node: defaultNode, + pod: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "bar", Name: "baz"}}, + expectedPatchResp: nil, }, { caseName: "no matching pod", @@ -96,9 +103,9 @@ func TestProxyTopologyInjector_Handle(t *testing.T) { Namespace: "bar", }, }, - node: defaultNode, - pod: defaultPod, - wantErr: true, + node: defaultNode, + pod: defaultPod, + expectedPatchResp: nil, }, } for _, tc := range cases { @@ -120,9 +127,7 @@ func TestProxyTopologyInjector_Handle(t *testing.T) { } objBytes, err := json.Marshal(tc.obj) - if err != nil { - t.Fatalf("failed to marshal object: %v", err) - } + require.NoError(t, err) req := admission.Request{ AdmissionRequest: admissionv1.AdmissionRequest{ @@ -135,23 +140,9 @@ func TestProxyTopologyInjector_Handle(t *testing.T) { } resp := mutator.Handle(context.Background(), req) + require.True(t, resp.Allowed) - if !resp.Allowed && tc.wantErr { - t.Fatalf("expected Allowed response, got: %v", resp.Result) - } - - updatedPod := &corev1.Pod{} - if err = fakeClient.Get(context.Background(), types.NamespacedName{Name: tc.pod.Name, Namespace: tc.pod.Namespace}, updatedPod); err != nil { - t.Fatalf("get pod: %v", err) - } - - zone, ok := updatedPod.Labels[corev1.LabelTopologyZone] - if tc.wantErr { - require.False(t, ok, "pod has unexpected topology label: %v", updatedPod) - } else { - require.True(t, ok, "pod does not have expected topology label: %v", updatedPod) - require.Equal(t, zone, tc.node.Labels[corev1.LabelTopologyZone]) - } + require.Equal(t, tc.expectedPatchResp, resp.Patches) }) } } From 563ebe231ea13a57d96c4d72e1b25dc0878dbfbc Mon Sep 17 00:00:00 2001 From: zirain Date: Tue, 6 May 2025 12:51:09 +0800 Subject: [PATCH 21/66] feat: allow merge rate limit rule in BTP (#5915) * feat: allow merge rate limit rule in BTP Signed-off-by: zirain * fix gen Signed-off-by: zirain --------- Signed-off-by: zirain Signed-off-by: Arko Dasgupta --- api/v1alpha1/ratelimit_types.go | 14 +++++- ....envoyproxy.io_backendtrafficpolicies.yaml | 10 ++++ ....envoyproxy.io_backendtrafficpolicies.yaml | 10 ++++ ...dtrafficpolicy_ratelimitrule_merge.in.yaml | 27 +++++++++++ ...icy_ratelimitrule_merge.jsonmerge.out.yaml | 29 ++++++++++++ ...afficpolicy_ratelimitrule_merge.patch.yaml | 27 +++++++++++ ...atelimitrule_merge.strategicmerge.out.yaml | 47 +++++++++++++++++++ site/content/en/latest/api/extension_types.md | 1 + test/helm/gateway-crds-helm/all.out.yaml | 10 ++++ .../envoy-gateway-crds.out.yaml | 10 ++++ 10 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.in.yaml create mode 100644 internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.jsonmerge.out.yaml create mode 100644 internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.patch.yaml create mode 100644 internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.strategicmerge.out.yaml diff --git a/api/v1alpha1/ratelimit_types.go b/api/v1alpha1/ratelimit_types.go index 7ecca9e904..21727bb057 100644 --- a/api/v1alpha1/ratelimit_types.go +++ b/api/v1alpha1/ratelimit_types.go @@ -49,8 +49,10 @@ type GlobalRateLimit struct { // matches two rules, one rate limited and one not, the final decision will be // to rate limit the request. // + // +patchMergeKey:"name" + // +patchStrategy:"merge" // +kubebuilder:validation:MaxItems=64 - Rules []RateLimitRule `json:"rules"` + Rules []RateLimitRule `json:"rules" patchMergeKey:"name" patchStrategy:"merge"` // Shared determines whether the rate limit rules apply across all the policy targets. // If set to true, the rule is treated as a common bucket and is shared across all policy targets (xRoutes). @@ -69,15 +71,23 @@ type LocalRateLimit struct { // matches two rules, one with 10rps and one with 20rps, the final limit will // be based on the rule with 10rps. // + // +patchMergeKey:"name" + // +patchStrategy:"merge" + // // +optional // +kubebuilder:validation:MaxItems=16 // +kubebuilder:validation:XValidation:rule="self.all(foo, !has(foo.cost) || !has(foo.cost.response))", message="response cost is not supported for Local Rate Limits" - Rules []RateLimitRule `json:"rules"` + Rules []RateLimitRule `json:"rules" patchMergeKey:"name" patchStrategy:"merge"` } // RateLimitRule defines the semantics for matching attributes // from the incoming requests, and setting limits for them. type RateLimitRule struct { + // Name is the name of the rule. This is used to identify the rule + // in the Envoy configuration and as a unique identifier for merging. + // + // +optional + Name string `json:"name,omitempty"` // 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 diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml index b58c751757..66c01520e6 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml @@ -948,6 +948,11 @@ spec: - requests - unit type: object + name: + description: |- + Name is the name of the rule. This is used to identify the rule + in the Envoy configuration and as a unique identifier for merging. + type: string required: - limit type: object @@ -1198,6 +1203,11 @@ spec: - requests - unit type: object + name: + description: |- + Name is the name of the rule. This is used to identify the rule + in the Envoy configuration and as a unique identifier for merging. + type: string required: - limit type: object diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml index fb186d33d6..af436290d9 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml @@ -947,6 +947,11 @@ spec: - requests - unit type: object + name: + description: |- + Name is the name of the rule. This is used to identify the rule + in the Envoy configuration and as a unique identifier for merging. + type: string required: - limit type: object @@ -1197,6 +1202,11 @@ spec: - requests - unit type: object + name: + description: |- + Name is the name of the rule. This is used to identify the rule + in the Envoy configuration and as a unique identifier for merging. + type: string required: - limit type: object diff --git a/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.in.yaml b/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.in.yaml new file mode 100644 index 0000000000..3dc8eee659 --- /dev/null +++ b/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.in.yaml @@ -0,0 +1,27 @@ +kind: BackendTrafficPolicy +metadata: + name: original +spec: + rateLimit: + type: Global + global: + rules: + - name: one + clientSelectors: + - headers: + - name: x-user-id + type: Exact + value: one + limit: + # 21 instead of 20 to test the zero request cost. + requests: 21 + unit: Hour + cost: + request: + from: Number + number: 0 + response: + from: Metadata + metadata: + namespace: io.envoyproxy.gateway.e2e + key: request_cost_set_by_ext_proc diff --git a/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.jsonmerge.out.yaml b/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.jsonmerge.out.yaml new file mode 100644 index 0000000000..a679b04d1b --- /dev/null +++ b/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.jsonmerge.out.yaml @@ -0,0 +1,29 @@ +kind: BackendTrafficPolicy +metadata: + creationTimestamp: null + name: original +spec: + rateLimit: + global: + rules: + - clientSelectors: + - headers: + - name: x-user-id + type: Exact + value: two + cost: + request: + from: Number + number: 0 + response: + from: Metadata + metadata: + key: request_cost_set_by_ext_proc + namespace: io.envoyproxy.gateway.e2e + limit: + requests: 21 + unit: Hour + name: two + type: Global +status: + ancestors: null diff --git a/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.patch.yaml b/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.patch.yaml new file mode 100644 index 0000000000..2d13bd03b0 --- /dev/null +++ b/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.patch.yaml @@ -0,0 +1,27 @@ +kind: BackendTrafficPolicy +metadata: + name: original +spec: + rateLimit: + type: Global + global: + rules: + - name: two + clientSelectors: + - headers: + - name: x-user-id + type: Exact + value: two + limit: + # 21 instead of 20 to test the zero request cost. + requests: 21 + unit: Hour + cost: + request: + from: Number + number: 0 + response: + from: Metadata + metadata: + namespace: io.envoyproxy.gateway.e2e + key: request_cost_set_by_ext_proc diff --git a/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.strategicmerge.out.yaml b/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.strategicmerge.out.yaml new file mode 100644 index 0000000000..1833716e3e --- /dev/null +++ b/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.strategicmerge.out.yaml @@ -0,0 +1,47 @@ +kind: BackendTrafficPolicy +metadata: + creationTimestamp: null + name: original +spec: + rateLimit: + global: + rules: + - clientSelectors: + - headers: + - name: x-user-id + type: Exact + value: two + cost: + request: + from: Number + number: 0 + response: + from: Metadata + metadata: + key: request_cost_set_by_ext_proc + namespace: io.envoyproxy.gateway.e2e + limit: + requests: 21 + unit: Hour + name: two + - clientSelectors: + - headers: + - name: x-user-id + type: Exact + value: one + cost: + request: + from: Number + number: 0 + response: + from: Metadata + metadata: + key: request_cost_set_by_ext_proc + namespace: io.envoyproxy.gateway.e2e + limit: + requests: 21 + unit: Hour + name: one + type: Global +status: + ancestors: null diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 5997d650b3..580c34083c 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -3870,6 +3870,7 @@ _Appears in:_ | Field | Type | Required | Default | Description | | --- | --- | --- | --- | --- | +| `name` | _string_ | false | | Name is the name of the rule. This is used to identify the rule
in the Envoy configuration and as a unique identifier for merging. | | `clientSelectors` | _[RateLimitSelectCondition](#ratelimitselectcondition) array_ | false | | 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 no client selectors are specified, the rule applies to all traffic of
the targeted Route.
If the policy targets a Gateway, the rule applies to each Route of the Gateway.
Please note that each Route has its own rate limit counters. For example,
if a Gateway has two Routes, and the policy has a rule with limit 10rps,
each Route will have its own 10rps limit. | | `limit` | _[RateLimitValue](#ratelimitvalue)_ | true | | 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. | | `cost` | _[RateLimitCost](#ratelimitcost)_ | false | | Cost specifies the cost of requests and responses for the rule.
This is optional and if not specified, the default behavior is to reduce the rate limit counters by 1 on
the request path and do not reduce the rate limit counters on the response path. | diff --git a/test/helm/gateway-crds-helm/all.out.yaml b/test/helm/gateway-crds-helm/all.out.yaml index e104fbec76..8d3a3a2403 100644 --- a/test/helm/gateway-crds-helm/all.out.yaml +++ b/test/helm/gateway-crds-helm/all.out.yaml @@ -18570,6 +18570,11 @@ spec: - requests - unit type: object + name: + description: |- + Name is the name of the rule. This is used to identify the rule + in the Envoy configuration and as a unique identifier for merging. + type: string required: - limit type: object @@ -18820,6 +18825,11 @@ spec: - requests - unit type: object + name: + description: |- + Name is the name of the rule. This is used to identify the rule + in the Envoy configuration and as a unique identifier for merging. + type: string required: - limit type: object diff --git a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml index 086ac96c89..a0e7257100 100644 --- a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml +++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml @@ -1258,6 +1258,11 @@ spec: - requests - unit type: object + name: + description: |- + Name is the name of the rule. This is used to identify the rule + in the Envoy configuration and as a unique identifier for merging. + type: string required: - limit type: object @@ -1508,6 +1513,11 @@ spec: - requests - unit type: object + name: + description: |- + Name is the name of the rule. This is used to identify the rule + in the Envoy configuration and as a unique identifier for merging. + type: string required: - limit type: object From e16c889eae0a75904f7144310fdcc0feac58f214 Mon Sep 17 00:00:00 2001 From: "Huabing (Robin) Zhao" Date: Wed, 7 May 2025 03:32:56 +0800 Subject: [PATCH 22/66] docs: install EG via Argo CD (#5824) * install EG via Argo CD Signed-off-by: Huabing (Robin) Zhao Signed-off-by: Arko Dasgupta --- .../en/latest/boilerplates/kind-cluster.md | 6 + .../en/latest/boilerplates/open-ports.md | 22 +++ .../en/latest/install/install-argocd.md | 128 ++++++++++++++++++ .../content/en/latest/install/install-helm.md | 37 +---- .../content/en/latest/install/install-yaml.md | 25 ++-- .../en/v1.3/boilerplates/kind-cluster.md | 6 + .../en/v1.3/boilerplates/open-ports.md | 22 +++ .../content/en/v1.3/install/install-argocd.md | 128 ++++++++++++++++++ site/content/en/v1.3/install/install-helm.md | 35 +---- site/content/en/v1.3/install/install-yaml.md | 25 ++-- 10 files changed, 345 insertions(+), 89 deletions(-) create mode 100644 site/content/en/latest/boilerplates/kind-cluster.md create mode 100644 site/content/en/latest/boilerplates/open-ports.md create mode 100644 site/content/en/latest/install/install-argocd.md create mode 100644 site/content/en/v1.3/boilerplates/kind-cluster.md create mode 100644 site/content/en/v1.3/boilerplates/open-ports.md create mode 100644 site/content/en/v1.3/install/install-argocd.md diff --git a/site/content/en/latest/boilerplates/kind-cluster.md b/site/content/en/latest/boilerplates/kind-cluster.md new file mode 100644 index 0000000000..643bb28cbe --- /dev/null +++ b/site/content/en/latest/boilerplates/kind-cluster.md @@ -0,0 +1,6 @@ +Envoy Gateway is typically deployed in a Kubernetes cluster. +If you don’t have one yet, you can use `kind` to create a local cluster for testing purposes. + +{{% alert title="Developer Guide" color="primary" %}} +Refer to the [Developer Guide](../../contributions/develop) to learn more. +{{% /alert %}} diff --git a/site/content/en/latest/boilerplates/open-ports.md b/site/content/en/latest/boilerplates/open-ports.md new file mode 100644 index 0000000000..f193c2d2d2 --- /dev/null +++ b/site/content/en/latest/boilerplates/open-ports.md @@ -0,0 +1,22 @@ +## Open Ports + +These are the ports used by Envoy Gateway and the managed Envoy Proxy. + +### Envoy Gateway + +| Envoy Gateway | Address | Port | Configurable | +| :-------------------: | :-------: | :---: | :----------: | +| Xds EnvoyProxy Server | 0.0.0.0 | 18000 | No | +| Xds RateLimit Server | 0.0.0.0 | 18001 | No | +| Admin Server | 127.0.0.1 | 19000 | Yes | +| Metrics Server | 0.0.0.0 | 19001 | No | +| Health Check | 127.0.0.1 | 8081 | No | + +### EnvoyProxy + +| Envoy Proxy | Address | Port | +| :--------------: | :-------: | :---: | +| Admin Server | 127.0.0.1 | 19000 | +| Stats | 0.0.0.0 | 19001 | +| Shutdown Manager | 0.0.0.0 | 19002 | +| Readiness | 0.0.0.0 | 19003 | diff --git a/site/content/en/latest/install/install-argocd.md b/site/content/en/latest/install/install-argocd.md new file mode 100644 index 0000000000..b04ec51262 --- /dev/null +++ b/site/content/en/latest/install/install-argocd.md @@ -0,0 +1,128 @@ ++++ +title = "Install with Argo CD" +weight = -99 ++++ + +[Argo CD](https://argo-cd.readthedocs.io) is a declarative, GitOps continuous delivery tool for Kubernetes. +Argo CD can be used to manage the deployment of Envoy Gateway on Kubernetes clusters. + +## Before you begin + +{{% alert title="Compatibility Matrix" color="warning" %}} +Refer to the [Version Compatibility Matrix](/news/releases/matrix) to learn more. +{{% /alert %}} + +{{< boilerplate kind-cluster >}} + +Argo CD must be installed in your Kubernetes cluster, and the argocd CLI must be available on your local machine. +If you haven’t set it up yet, you can follow the [official installation guide](https://argo-cd.readthedocs.io/en/stable/operator-manual/installation/) to install Argo CD. + +## Install with Argo CD + +Create a new Argo CD Application that pulls the Envoy Gateway Helm chart as its source. + +```shell +cat <}} + destination: + namespace: envoy-gateway-system + server: https://kubernetes.default.svc + syncPolicy: + syncOptions: + - CreateNamespace=true + - ServerSideApply=true + automated: + prune: true + selfHeal: true +EOF +``` + +**Note**: + +* Set `ServerSideApply` to `true` to enable Kubernetes [server-side apply](https://kubernetes.io/docs/reference/using-api/server-side-apply/). This helps avoid the 262,144-byte annotation size limit. +* For simplicity, we apply the Application resource directly to the cluster. +In a production environment, it’s recommended to store this configuration in a Git repository and manage it using another Argo CD Application that uses Git as its source — following a GitOps workflow. + +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/{{< yaml-version >}}/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/{{< yaml-version >}}/quickstart.yaml + + +## Helm chart customizations + +You can customize the Envoy Gateway installation by using the Helm chart values. + +{{% alert title="Helm Chart Values" color="primary" %}} +If you want to know all the available fields inside the values.yaml file, please see the [Helm Chart Values](./gateway-helm-api). +{{% /alert %}} + +Below is an example of how to customize the Envoy Gateway installation by using the `valuesObject` field in the Argo CD Application. + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: envoy-gateway + namespace: argocd +spec: + project: default + source: + helm: + valuesObject: + deployment: + envoyGateway: + resources: + limits: + cpu: 700m + memory: 256Mi + chart: gateway-helm + path: gateway-helm + repoURL: docker.io/envoyproxy + targetRevision: {{< helm-version >}} + destination: + namespace: envoy-gateway-system + server: https://kubernetes.default.svc + syncPolicy: + syncOptions: + - CreateNamespace=true + - ServerSideApply=true + automated: + prune: true + selfHeal: true +``` + +Argo CD supports multiple ways of specifying Helm chart values, you can find more details in the [Argo CD documentation](https://argo-cd.readthedocs.io/en/stable/user-guide/helm/#helm). + +{{< boilerplate open-ports >}} + +{{% alert title="Next Steps" color="warning" %}} +Envoy Gateway should now be successfully installed and running. To experience more abilities of Envoy Gateway, refer to [Tasks](../tasks). +{{% /alert %}} diff --git a/site/content/en/latest/install/install-helm.md b/site/content/en/latest/install/install-helm.md index d146194d35..34b640c83d 100644 --- a/site/content/en/latest/install/install-helm.md +++ b/site/content/en/latest/install/install-helm.md @@ -3,7 +3,7 @@ title = "Install with Helm" weight = -100 +++ -[Helm](https://helm.sh) is a package manager for Kubernetes that automates the release and management of software on Kubernetes. +[Helm](https://helm.sh) is a package manager for Kubernetes that automates the release and management of software on Kubernetes. Envoy Gateway can be installed via a Helm chart with a few simple steps, depending on if you are deploying for the first time, upgrading Envoy Gateway from an existing installation, or migrating from Envoy Gateway. @@ -13,6 +13,8 @@ Envoy Gateway can be installed via a Helm chart with a few simple steps, dependi Refer to the [Version Compatibility Matrix](/news/releases/matrix) to learn more. {{% /alert %}} +{{< boilerplate kind-cluster >}} + The Envoy Gateway Helm chart is hosted by DockerHub. It is published at `oci://docker.io/envoyproxy/gateway-helm`. @@ -25,12 +27,6 @@ You can visit [Envoy Gateway Helm Chart](https://hub.docker.com/r/envoyproxy/gat ## Install with Helm -Envoy Gateway is typically deployed to Kubernetes from the command line. If you don't have Kubernetes, you should use `kind` to create one. - -{{% alert title="Developer Guide" color="primary" %}} -Refer to the [Developer Guide](../../contributions/develop) to learn more. -{{% /alert %}} - Install the Gateway API CRDs and Envoy Gateway: ```shell @@ -59,11 +55,11 @@ consideration when debugging. [`quickstart.yaml`]: https://github.com/envoyproxy/gateway/releases/download/{{< yaml-version >}}/quickstart.yaml -## Upgrading from a previous version +## Upgrading from the previous version [Helm](https://helm.sh/docs/chart_best_practices/custom_resource_definitions/#some-caveats-and-explanations) does not update CRDs that live in the `/crds` folder in the Helm Chart. So you will manually need to update the CRDs. -Follow the steps outlined in [this](./install-yaml/#upgrading-from-v1.2) section if you're upgrading from a previous version. +Follow the steps outlined in [this](./install-yaml/#upgrading-from-a-previous-version) section if you're upgrading from a previous version. ## Helm chart customizations @@ -124,28 +120,7 @@ You can use the below command to install the envoy gateway using values.yaml fil helm install eg oci://docker.io/envoyproxy/gateway-helm --version {{< helm-version >}} -n envoy-gateway-system --create-namespace -f values.yaml ``` -## Open Ports - -These are the ports used by Envoy Gateway and the managed Envoy Proxy. - -### Envoy Gateway - -| Envoy Gateway | Address | Port | Configurable | -| :-------------------: | :-------: | :---: | :----------: | -| Xds EnvoyProxy Server | 0.0.0.0 | 18000 | No | -| Xds RateLimit Server | 0.0.0.0 | 18001 | No | -| Admin Server | 127.0.0.1 | 19000 | Yes | -| Metrics Server | 0.0.0.0 | 19001 | No | -| Health Check | 127.0.0.1 | 8081 | No | - -### EnvoyProxy - -| Envoy Proxy | Address | Port | -| :--------------: | :-------: | :---: | -| Admin Server | 127.0.0.1 | 19000 | -| Stats | 0.0.0.0 | 19001 | -| Shutdown Manager | 0.0.0.0 | 19002 | -| Readiness | 0.0.0.0 | 19003 | +{{< boilerplate open-ports >}} {{% alert title="Next Steps" color="warning" %}} Envoy Gateway should now be successfully installed and running. To experience more abilities of Envoy Gateway, refer to [Tasks](../tasks). diff --git a/site/content/en/latest/install/install-yaml.md b/site/content/en/latest/install/install-yaml.md index eac18063e3..578e5036db 100644 --- a/site/content/en/latest/install/install-yaml.md +++ b/site/content/en/latest/install/install-yaml.md @@ -1,6 +1,6 @@ +++ title = "Install with Kubernetes YAML" -weight = -99 +weight = -98 +++ This task walks you through installing Envoy Gateway in your Kubernetes cluster. @@ -11,22 +11,13 @@ installation, it is recommended that you use helm. ## Before you begin -Envoy Gateway is designed to run in Kubernetes for production. The most essential requirements are: - -* Kubernetes 1.28 or later -* The `kubectl` command-line tool - {{% alert title="Compatibility Matrix" color="warning" %}} Refer to the [Version Compatibility Matrix](/news/releases/matrix) to learn more. {{% /alert %}} -## Install with YAML +{{< boilerplate kind-cluster >}} -Envoy Gateway is typically deployed to Kubernetes from the command line. If you don't have Kubernetes, you should use `kind` to create one. - -{{% alert title="Developer Guide" color="primary" %}} -Refer to the [Developer Guide](../../contributions/develop) to learn more. -{{% /alert %}} +## Install with YAML 1. In your terminal, run the following command: @@ -38,9 +29,9 @@ Refer to the [Developer Guide](../../contributions/develop) to learn more. Envoy Gateway should now be successfully installed and running, but in order to experience more abilities of Envoy Gateway, you can refer to [Tasks](/latest/tasks). -## Upgrading from v1.2 +## Upgrading from the previous version -Some manual migration steps are required to upgrade Envoy Gateway to v1.3. +Some manual migration steps are required to upgrade Envoy Gateway. 1. Update Gateway-API and Envoy Gateway CRDs: @@ -55,3 +46,9 @@ kubectl apply --force-conflicts --server-side -f ./gateway-helm/crds/generated ```shell helm upgrade eg oci://docker.io/envoyproxy/gateway-helm --version {{< yaml-version >}} -n envoy-gateway-system ``` + +{{< boilerplate open-ports >}} + +{{% alert title="Next Steps" color="warning" %}} +Envoy Gateway should now be successfully installed and running. To experience more abilities of Envoy Gateway, refer to [Tasks](../tasks). +{{% /alert %}} diff --git a/site/content/en/v1.3/boilerplates/kind-cluster.md b/site/content/en/v1.3/boilerplates/kind-cluster.md new file mode 100644 index 0000000000..643bb28cbe --- /dev/null +++ b/site/content/en/v1.3/boilerplates/kind-cluster.md @@ -0,0 +1,6 @@ +Envoy Gateway is typically deployed in a Kubernetes cluster. +If you don’t have one yet, you can use `kind` to create a local cluster for testing purposes. + +{{% alert title="Developer Guide" color="primary" %}} +Refer to the [Developer Guide](../../contributions/develop) to learn more. +{{% /alert %}} diff --git a/site/content/en/v1.3/boilerplates/open-ports.md b/site/content/en/v1.3/boilerplates/open-ports.md new file mode 100644 index 0000000000..f193c2d2d2 --- /dev/null +++ b/site/content/en/v1.3/boilerplates/open-ports.md @@ -0,0 +1,22 @@ +## Open Ports + +These are the ports used by Envoy Gateway and the managed Envoy Proxy. + +### Envoy Gateway + +| Envoy Gateway | Address | Port | Configurable | +| :-------------------: | :-------: | :---: | :----------: | +| Xds EnvoyProxy Server | 0.0.0.0 | 18000 | No | +| Xds RateLimit Server | 0.0.0.0 | 18001 | No | +| Admin Server | 127.0.0.1 | 19000 | Yes | +| Metrics Server | 0.0.0.0 | 19001 | No | +| Health Check | 127.0.0.1 | 8081 | No | + +### EnvoyProxy + +| Envoy Proxy | Address | Port | +| :--------------: | :-------: | :---: | +| Admin Server | 127.0.0.1 | 19000 | +| Stats | 0.0.0.0 | 19001 | +| Shutdown Manager | 0.0.0.0 | 19002 | +| Readiness | 0.0.0.0 | 19003 | diff --git a/site/content/en/v1.3/install/install-argocd.md b/site/content/en/v1.3/install/install-argocd.md new file mode 100644 index 0000000000..b04ec51262 --- /dev/null +++ b/site/content/en/v1.3/install/install-argocd.md @@ -0,0 +1,128 @@ ++++ +title = "Install with Argo CD" +weight = -99 ++++ + +[Argo CD](https://argo-cd.readthedocs.io) is a declarative, GitOps continuous delivery tool for Kubernetes. +Argo CD can be used to manage the deployment of Envoy Gateway on Kubernetes clusters. + +## Before you begin + +{{% alert title="Compatibility Matrix" color="warning" %}} +Refer to the [Version Compatibility Matrix](/news/releases/matrix) to learn more. +{{% /alert %}} + +{{< boilerplate kind-cluster >}} + +Argo CD must be installed in your Kubernetes cluster, and the argocd CLI must be available on your local machine. +If you haven’t set it up yet, you can follow the [official installation guide](https://argo-cd.readthedocs.io/en/stable/operator-manual/installation/) to install Argo CD. + +## Install with Argo CD + +Create a new Argo CD Application that pulls the Envoy Gateway Helm chart as its source. + +```shell +cat <}} + destination: + namespace: envoy-gateway-system + server: https://kubernetes.default.svc + syncPolicy: + syncOptions: + - CreateNamespace=true + - ServerSideApply=true + automated: + prune: true + selfHeal: true +EOF +``` + +**Note**: + +* Set `ServerSideApply` to `true` to enable Kubernetes [server-side apply](https://kubernetes.io/docs/reference/using-api/server-side-apply/). This helps avoid the 262,144-byte annotation size limit. +* For simplicity, we apply the Application resource directly to the cluster. +In a production environment, it’s recommended to store this configuration in a Git repository and manage it using another Argo CD Application that uses Git as its source — following a GitOps workflow. + +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/{{< yaml-version >}}/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/{{< yaml-version >}}/quickstart.yaml + + +## Helm chart customizations + +You can customize the Envoy Gateway installation by using the Helm chart values. + +{{% alert title="Helm Chart Values" color="primary" %}} +If you want to know all the available fields inside the values.yaml file, please see the [Helm Chart Values](./gateway-helm-api). +{{% /alert %}} + +Below is an example of how to customize the Envoy Gateway installation by using the `valuesObject` field in the Argo CD Application. + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: envoy-gateway + namespace: argocd +spec: + project: default + source: + helm: + valuesObject: + deployment: + envoyGateway: + resources: + limits: + cpu: 700m + memory: 256Mi + chart: gateway-helm + path: gateway-helm + repoURL: docker.io/envoyproxy + targetRevision: {{< helm-version >}} + destination: + namespace: envoy-gateway-system + server: https://kubernetes.default.svc + syncPolicy: + syncOptions: + - CreateNamespace=true + - ServerSideApply=true + automated: + prune: true + selfHeal: true +``` + +Argo CD supports multiple ways of specifying Helm chart values, you can find more details in the [Argo CD documentation](https://argo-cd.readthedocs.io/en/stable/user-guide/helm/#helm). + +{{< boilerplate open-ports >}} + +{{% alert title="Next Steps" color="warning" %}} +Envoy Gateway should now be successfully installed and running. To experience more abilities of Envoy Gateway, refer to [Tasks](../tasks). +{{% /alert %}} diff --git a/site/content/en/v1.3/install/install-helm.md b/site/content/en/v1.3/install/install-helm.md index d146194d35..7fb15b7087 100644 --- a/site/content/en/v1.3/install/install-helm.md +++ b/site/content/en/v1.3/install/install-helm.md @@ -3,7 +3,7 @@ title = "Install with Helm" weight = -100 +++ -[Helm](https://helm.sh) is a package manager for Kubernetes that automates the release and management of software on Kubernetes. +[Helm](https://helm.sh) is a package manager for Kubernetes that automates the release and management of software on Kubernetes. Envoy Gateway can be installed via a Helm chart with a few simple steps, depending on if you are deploying for the first time, upgrading Envoy Gateway from an existing installation, or migrating from Envoy Gateway. @@ -13,6 +13,8 @@ Envoy Gateway can be installed via a Helm chart with a few simple steps, dependi Refer to the [Version Compatibility Matrix](/news/releases/matrix) to learn more. {{% /alert %}} +{{< boilerplate kind-cluster >}} + The Envoy Gateway Helm chart is hosted by DockerHub. It is published at `oci://docker.io/envoyproxy/gateway-helm`. @@ -25,12 +27,6 @@ You can visit [Envoy Gateway Helm Chart](https://hub.docker.com/r/envoyproxy/gat ## Install with Helm -Envoy Gateway is typically deployed to Kubernetes from the command line. If you don't have Kubernetes, you should use `kind` to create one. - -{{% alert title="Developer Guide" color="primary" %}} -Refer to the [Developer Guide](../../contributions/develop) to learn more. -{{% /alert %}} - Install the Gateway API CRDs and Envoy Gateway: ```shell @@ -63,7 +59,7 @@ consideration when debugging. [Helm](https://helm.sh/docs/chart_best_practices/custom_resource_definitions/#some-caveats-and-explanations) does not update CRDs that live in the `/crds` folder in the Helm Chart. So you will manually need to update the CRDs. -Follow the steps outlined in [this](./install-yaml/#upgrading-from-v1.2) section if you're upgrading from a previous version. +Follow the steps outlined in [this](./install-yaml/#upgrading-from-a-previous-version) section if you're upgrading from a previous version. ## Helm chart customizations @@ -124,28 +120,7 @@ You can use the below command to install the envoy gateway using values.yaml fil helm install eg oci://docker.io/envoyproxy/gateway-helm --version {{< helm-version >}} -n envoy-gateway-system --create-namespace -f values.yaml ``` -## Open Ports - -These are the ports used by Envoy Gateway and the managed Envoy Proxy. - -### Envoy Gateway - -| Envoy Gateway | Address | Port | Configurable | -| :-------------------: | :-------: | :---: | :----------: | -| Xds EnvoyProxy Server | 0.0.0.0 | 18000 | No | -| Xds RateLimit Server | 0.0.0.0 | 18001 | No | -| Admin Server | 127.0.0.1 | 19000 | Yes | -| Metrics Server | 0.0.0.0 | 19001 | No | -| Health Check | 127.0.0.1 | 8081 | No | - -### EnvoyProxy - -| Envoy Proxy | Address | Port | -| :--------------: | :-------: | :---: | -| Admin Server | 127.0.0.1 | 19000 | -| Stats | 0.0.0.0 | 19001 | -| Shutdown Manager | 0.0.0.0 | 19002 | -| Readiness | 0.0.0.0 | 19003 | +{{< boilerplate open-ports >}} {{% alert title="Next Steps" color="warning" %}} Envoy Gateway should now be successfully installed and running. To experience more abilities of Envoy Gateway, refer to [Tasks](../tasks). diff --git a/site/content/en/v1.3/install/install-yaml.md b/site/content/en/v1.3/install/install-yaml.md index eac18063e3..1fa0fcc29a 100644 --- a/site/content/en/v1.3/install/install-yaml.md +++ b/site/content/en/v1.3/install/install-yaml.md @@ -1,6 +1,6 @@ +++ title = "Install with Kubernetes YAML" -weight = -99 +weight = -98 +++ This task walks you through installing Envoy Gateway in your Kubernetes cluster. @@ -11,22 +11,13 @@ installation, it is recommended that you use helm. ## Before you begin -Envoy Gateway is designed to run in Kubernetes for production. The most essential requirements are: - -* Kubernetes 1.28 or later -* The `kubectl` command-line tool - {{% alert title="Compatibility Matrix" color="warning" %}} Refer to the [Version Compatibility Matrix](/news/releases/matrix) to learn more. {{% /alert %}} -## Install with YAML +{{< boilerplate kind-cluster >}} -Envoy Gateway is typically deployed to Kubernetes from the command line. If you don't have Kubernetes, you should use `kind` to create one. - -{{% alert title="Developer Guide" color="primary" %}} -Refer to the [Developer Guide](../../contributions/develop) to learn more. -{{% /alert %}} +## Install with YAML 1. In your terminal, run the following command: @@ -38,9 +29,9 @@ Refer to the [Developer Guide](../../contributions/develop) to learn more. Envoy Gateway should now be successfully installed and running, but in order to experience more abilities of Envoy Gateway, you can refer to [Tasks](/latest/tasks). -## Upgrading from v1.2 +## Upgrading from a previous version -Some manual migration steps are required to upgrade Envoy Gateway to v1.3. +Some manual migration steps are required to upgrade Envoy Gateway. 1. Update Gateway-API and Envoy Gateway CRDs: @@ -55,3 +46,9 @@ kubectl apply --force-conflicts --server-side -f ./gateway-helm/crds/generated ```shell helm upgrade eg oci://docker.io/envoyproxy/gateway-helm --version {{< yaml-version >}} -n envoy-gateway-system ``` + +{{< boilerplate open-ports >}} + +{{% alert title="Next Steps" color="warning" %}} +Envoy Gateway should now be successfully installed and running. To experience more abilities of Envoy Gateway, refer to [Tasks](../tasks). +{{% /alert %}} From 787bc4719e042436ae48b3397c3e2810f2581250 Mon Sep 17 00:00:00 2001 From: "Huabing (Robin) Zhao" Date: Wed, 7 May 2025 08:34:37 +0800 Subject: [PATCH 23/66] chore: clean up BTP status (#5934) clean up BTP status Signed-off-by: Huabing (Robin) Zhao Signed-off-by: Arko Dasgupta --- internal/gatewayapi/backendtlspolicy.go | 19 ++++++------------- ...nvoyproxy-tls-settings-invalid-ns.out.yaml | 8 +++----- .../envoyproxy-tls-settings-invalid.out.yaml | 7 +++---- 3 files changed, 12 insertions(+), 22 deletions(-) diff --git a/internal/gatewayapi/backendtlspolicy.go b/internal/gatewayapi/backendtlspolicy.go index c6bc0b8bba..143bc749fd 100644 --- a/internal/gatewayapi/backendtlspolicy.go +++ b/internal/gatewayapi/backendtlspolicy.go @@ -44,19 +44,12 @@ func (t *Translator) applyBackendTLSSetting( return t.applyEnvoyProxyBackendTLSSetting(tlsBundle, resources, envoyProxy) } - upstreamConfig, policy, err := t.processBackendTLSPolicy(backendRef, backendNamespace, parent, resources) + upstreamConfig, err := t.processBackendTLSPolicy(backendRef, backendNamespace, parent, resources) if err != nil { return nil, err } - // TODO: zhaohuabing is it correct to surface the EnvoyProxy TLS error to the policy? - // We probably should just remove this, the TLS error is already reported in the route status as "InvalidBackendTLS", - // and the BackendTLSPolicy is configured correctly. + if tlsBundle, err = t.applyEnvoyProxyBackendTLSSetting(upstreamConfig, resources, envoyProxy); err != nil { - status.SetTranslationErrorForPolicyAncestors(&policy.Status, - []gwapiv1a2.ParentReference{parent}, - t.GatewayControllerName, - policy.Generation, - status.Error2ConditionMsg(err)) return nil, err } return tlsBundle, nil @@ -101,10 +94,10 @@ func (t *Translator) processBackendTLSPolicy( backendNamespace string, parent gwapiv1a2.ParentReference, resources *resource.Resources, -) (*ir.TLSUpstreamConfig, *gwapiv1a3.BackendTLSPolicy, error) { +) (*ir.TLSUpstreamConfig, error) { policy := getBackendTLSPolicy(resources.BackendTLSPolicies, backendRef, backendNamespace, resources) if policy == nil { - return nil, nil, nil + return nil, nil } tlsBundle, err := getBackendTLSBundle(policy, resources) @@ -118,11 +111,11 @@ func (t *Translator) processBackendTLSPolicy( policy.Generation, status.Error2ConditionMsg(err), ) - return nil, nil, err + return nil, err } status.SetAcceptedForPolicyAncestors(&policy.Status, ancestorRefs, t.GatewayControllerName) - return tlsBundle, policy, nil + return tlsBundle, nil } func (t *Translator) applyEnvoyProxyBackendTLSSetting(tlsConfig *ir.TLSUpstreamConfig, resources *resource.Resources, ep *egv1a1.EnvoyProxy) (*ir.TLSUpstreamConfig, error) { diff --git a/internal/gatewayapi/testdata/envoyproxy-tls-settings-invalid-ns.out.yaml b/internal/gatewayapi/testdata/envoyproxy-tls-settings-invalid-ns.out.yaml index 7c872f644e..98a08206af 100644 --- a/internal/gatewayapi/testdata/envoyproxy-tls-settings-invalid-ns.out.yaml +++ b/internal/gatewayapi/testdata/envoyproxy-tls-settings-invalid-ns.out.yaml @@ -20,11 +20,9 @@ backendTLSPolicies: namespace: envoy-gateway conditions: - lastTransitionTime: null - message: 'ClientCertificateRef Secret is not located in the same namespace - as Envoyproxy. Secret namespace: envoy-gateway-user-ns does not match Envoyproxy - namespace: envoy-gateway-system.' - reason: Invalid - status: "False" + message: Policy has been accepted. + reason: Accepted + status: "True" type: Accepted controllerName: gateway.envoyproxy.io/gatewayclass-controller gateways: diff --git a/internal/gatewayapi/testdata/envoyproxy-tls-settings-invalid.out.yaml b/internal/gatewayapi/testdata/envoyproxy-tls-settings-invalid.out.yaml index d4224abd76..775ff25869 100644 --- a/internal/gatewayapi/testdata/envoyproxy-tls-settings-invalid.out.yaml +++ b/internal/gatewayapi/testdata/envoyproxy-tls-settings-invalid.out.yaml @@ -20,10 +20,9 @@ backendTLSPolicies: namespace: envoy-gateway conditions: - lastTransitionTime: null - message: 'Failed to locate TLS secret for client auth: envoy-gateway-system/client-auth-not-found - specified in EnvoyProxy envoy-gateway-system/test.' - reason: Invalid - status: "False" + message: Policy has been accepted. + reason: Accepted + status: "True" type: Accepted controllerName: gateway.envoyproxy.io/gatewayclass-controller gateways: From d896202b1b7566bbf9a6658452743f95f77c5e1b Mon Sep 17 00:00:00 2001 From: "Huabing (Robin) Zhao" Date: Wed, 7 May 2025 09:01:09 +0800 Subject: [PATCH 24/66] e2e: test for dynamic resolver backend using system ca for TLS (#5932) e2e test for dynamic resolver backend using system ca for TLS Signed-off-by: Huabing (Robin) Zhao Signed-off-by: Arko Dasgupta --- ...c-resolver-backend-with-tls-system-ca.yaml | 33 +++++++++++++++++++ ...httproute_with_dynamic_resolver_backend.go | 22 +++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 test/e2e/testdata/httproute-with-dynamic-resolver-backend-with-tls-system-ca.yaml diff --git a/test/e2e/testdata/httproute-with-dynamic-resolver-backend-with-tls-system-ca.yaml b/test/e2e/testdata/httproute-with-dynamic-resolver-backend-with-tls-system-ca.yaml new file mode 100644 index 0000000000..45ce6a40db --- /dev/null +++ b/test/e2e/testdata/httproute-with-dynamic-resolver-backend-with-tls-system-ca.yaml @@ -0,0 +1,33 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: httproute-with-dynamic-resolver-backend-tls-system-trust-store + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + rules: + - backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend-dynamic-resolver-tls-system-trust-store + matches: + - path: + type: PathPrefix + value: /with-tls-system-trust-store + filters: + - type: URLRewrite # rewrite the path to /, as we use gateway.envoyproxy.io to test the dynamic resolver, and this path is not available + urlRewrite: + path: + type: ReplaceFullPath + replaceFullPath: / +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: Backend +metadata: + name: backend-dynamic-resolver-tls-system-trust-store + namespace: gateway-conformance-infra +spec: + type: DynamicResolver + tls: + wellKnownCACertificates: System diff --git a/test/e2e/tests/httproute_with_dynamic_resolver_backend.go b/test/e2e/tests/httproute_with_dynamic_resolver_backend.go index d7509b0777..9168431c46 100644 --- a/test/e2e/tests/httproute_with_dynamic_resolver_backend.go +++ b/test/e2e/tests/httproute_with_dynamic_resolver_backend.go @@ -26,6 +26,7 @@ var DynamicResolverBackendTest = suite.ConformanceTest{ Manifests: []string{ "testdata/httproute-with-dynamic-resolver-backend.yaml", "testdata/httproute-with-dynamic-resolver-backend-with-tls.yaml", + "testdata/httproute-with-dynamic-resolver-backend-with-tls-system-ca.yaml", }, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { ns := "gateway-conformance-infra" @@ -80,5 +81,26 @@ var DynamicResolverBackendTest = suite.ConformanceTest{ http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse) }) + t.Run("route to service with TLS using system CA", func(t *testing.T) { + routeNN := types.NamespacedName{Name: "httproute-with-dynamic-resolver-backend-tls-system-trust-store", Namespace: ns} + gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) + BackendMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "backend-dynamic-resolver-tls-system-trust-store", Namespace: ns}) + + expectedResponse := http.ExpectedResponse{ + Request: http.Request{ + Host: "gateway.envoyproxy.io:443", + Path: "/with-tls-system-trust-store", + }, + ExpectedRequest: &http.ExpectedRequest{ + Request: http.Request{ + Host: "", + }, + }, + Response: http.Response{ + StatusCode: 200, + }, + } + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse) + }) }, } From cf8ef166fd67c3503157d4a42fd07b04f1d18daa Mon Sep 17 00:00:00 2001 From: sh2 Date: Wed, 7 May 2025 15:42:20 +0800 Subject: [PATCH 25/66] feat: implement offline kubernetes controller (#5767) Signed-off-by: Arko Dasgupta --- internal/provider/file/file.go | 83 +++-- internal/provider/file/file_test.go | 183 +++++++--- internal/provider/file/status.go | 342 ++++-------------- internal/provider/file/store.go | 339 +++++++++++++++-- .../{resources.all.yaml => resources.1.yaml} | 22 +- .../provider/file/testdata/resources.2.yaml | 76 ++++ .../provider/file/testdata/resources.tmpl | 4 +- .../provider/kubernetes/controller_offline.go | 131 +++++++ .../kubernetes/controller_offline_test.go | 49 +++ .../provider/kubernetes/controller_test.go | 2 +- internal/provider/kubernetes/indexers.go | 168 ++++----- .../provider/kubernetes/status_updater.go | 6 +- internal/provider/runner/runner.go | 6 +- 13 files changed, 936 insertions(+), 475 deletions(-) rename internal/provider/file/testdata/{resources.all.yaml => resources.1.yaml} (83%) create mode 100644 internal/provider/file/testdata/resources.2.yaml create mode 100644 internal/provider/kubernetes/controller_offline.go create mode 100644 internal/provider/kubernetes/controller_offline_test.go diff --git a/internal/provider/file/file.go b/internal/provider/file/file.go index 15777137bb..94bbf2b37c 100644 --- a/internal/provider/file/file.go +++ b/internal/provider/file/file.go @@ -13,6 +13,7 @@ import ( "os" "path/filepath" "strings" + "sync" "sync/atomic" "time" @@ -25,33 +26,45 @@ import ( "github.com/envoyproxy/gateway/internal/envoygateway/config" "github.com/envoyproxy/gateway/internal/filewatcher" "github.com/envoyproxy/gateway/internal/message" + "github.com/envoyproxy/gateway/internal/provider/kubernetes" "github.com/envoyproxy/gateway/internal/utils/path" ) type Provider struct { - paths []string - logger logr.Logger - watcher filewatcher.FileWatcher - resourcesStore *resourcesStore - extensionManagerEnabled bool + paths []string + logger logr.Logger + watcher filewatcher.FileWatcher + resources *message.ProviderResources + reconciler *kubernetes.OfflineGatewayAPIReconciler + store *resourcesStore + status *StatusHandler // ready indicates whether the provider can start watching filesystem events. ready atomic.Bool } -func New(svr *config.Server, resources *message.ProviderResources) (*Provider, error) { +func New(ctx context.Context, svr *config.Server, resources *message.ProviderResources) (*Provider, error) { logger := svr.Logger.Logger paths := sets.New[string]() if svr.EnvoyGateway.Provider.Custom.Resource.File != nil { paths.Insert(svr.EnvoyGateway.Provider.Custom.Resource.File.Paths...) } + // Create gateway-api offline reconciler. + statusHandler := NewStatusHandler(logger) + reconciler, err := kubernetes.NewOfflineGatewayAPIController(ctx, svr, statusHandler.Writer(), resources) + if err != nil { + return nil, fmt.Errorf("failed to create offline gateway-api controller") + } + return &Provider{ - paths: paths.UnsortedList(), - logger: logger, - watcher: filewatcher.NewWatcher(), - resourcesStore: newResourcesStore(svr.EnvoyGateway.Gateway.ControllerName, resources, logger), - extensionManagerEnabled: svr.EnvoyGateway.ExtensionManager != nil, + paths: paths.UnsortedList(), + logger: logger, + watcher: filewatcher.NewWatcher(), + resources: resources, + reconciler: reconciler, + store: newResourcesStore(svr.EnvoyGateway.Gateway.ControllerName, reconciler.Client, resources, logger), + status: statusHandler, }, nil } @@ -73,13 +86,18 @@ func (p *Provider) Start(ctx context.Context) error { } go p.startHealthProbeServer(ctx, readyzChecker) - // Subscribe resources status. - p.subscribeAndUpdateStatus(ctx) + // Offline controller should be started before initial resources load. + // Nor we may lose some messages from controller. + wg := new(sync.WaitGroup) + wg.Add(2) + go p.startReconciling(ctx, wg) + go p.status.Start(ctx, wg) + wg.Wait() initDirs, initFiles := path.ListDirsAndFiles(p.paths) - // Initially load resources from paths on host. - if err := p.resourcesStore.LoadAndStore(initFiles.UnsortedList(), initDirs.UnsortedList()); err != nil { - return fmt.Errorf("failed to load resources into store: %w", err) + // Initially load resources. + if err := p.store.ReloadAll(ctx, initFiles.UnsortedList(), initDirs.UnsortedList()); err != nil { + p.logger.Error(err, "failed to reload resources initially") } // Add paths to the watcher, and aggregate all path channels into one. @@ -150,18 +168,31 @@ func (p *Provider) Start(ctx context.Context) error { } p.logger.Info("file changed", "op", event.Op, "name", event.Name, "dir", filepath.Dir(event.Name)) - switch event.Op { - case fsnotify.Create, fsnotify.Write, fsnotify.Remove: - // Since we do not watch any events in the subdirectories, any events involving files - // modifications in current directory will trigger the event handling. - goto handle - default: - // do nothing - continue + handle: + if err := p.store.ReloadAll(ctx, curFiles.UnsortedList(), curDirs.UnsortedList()); err != nil { + p.logger.Error(err, "error when reload resources", "op", event.Op, "name", event.Name) } + } + } +} - handle: - p.resourcesStore.HandleEvent(curFiles.UnsortedList(), curDirs.UnsortedList()) +// startReconciling starts reconcile on offline controller when receiving signal from resources store. +func (p *Provider) startReconciling(ctx context.Context, ready *sync.WaitGroup) { + p.logger.Info("start reconciling") + defer p.logger.Info("stop reconciling") + ready.Done() + + for { + select { + case rid := <-p.store.reconcile: + p.logger.Info("start reconcile", "id", rid, "time", time.Now()) + if err := p.reconciler.Reconcile(ctx); err != nil { + p.logger.Error(err, "failed to reconcile", "id", rid) + } + p.logger.Info("reconcile finished", "id", rid, "time", time.Now()) + + case <-ctx.Done(): + return } } } diff --git a/internal/provider/file/file_test.go b/internal/provider/file/file_test.go index 57909ea696..89db93f763 100644 --- a/internal/provider/file/file_test.go +++ b/internal/provider/file/file_test.go @@ -6,6 +6,7 @@ package file import ( + "bytes" "context" "html/template" "io" @@ -18,6 +19,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/yaml" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" @@ -36,16 +38,32 @@ type resourcesParam struct { GatewayName string GatewayListenerPort string HTTPRouteName string + HTTPRouteHostname string BackendName string + EndpointPort string } -func newDefaultResourcesParam() *resourcesParam { +func newResourcesParam1() *resourcesParam { return &resourcesParam{ - GatewayClassName: "eg", - GatewayName: "eg", - GatewayListenerPort: "8888", - HTTPRouteName: "backend", - BackendName: "backend", + GatewayClassName: "eg-1", + GatewayName: "eg-1", + GatewayListenerPort: "8801", + HTTPRouteName: "backend-1", + HTTPRouteHostname: "www.test1.com", + BackendName: "backend-1", + EndpointPort: "3001", + } +} + +func newResourcesParam2() *resourcesParam { + return &resourcesParam{ + GatewayClassName: "eg-2", + GatewayName: "eg-2", + GatewayListenerPort: "8802", + HTTPRouteName: "backend-2", + HTTPRouteHostname: "www.test2.com", + BackendName: "backend-2", + EndpointPort: "3002", } } @@ -70,22 +88,24 @@ func newFileProviderConfig(paths []string) (*config.Server, error) { } func TestFileProvider(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + watchFileBase, _ := os.MkdirTemp(os.TempDir(), "test-files-*") watchFilePath := filepath.Join(watchFileBase, "test.yaml") watchDirPath, _ := os.MkdirTemp(os.TempDir(), "test-dir-*") // Prepare the watched test file. - writeResourcesFile(t, "testdata/resources.tmpl", watchFilePath, newDefaultResourcesParam()) + writeResourcesFile(t, watchFilePath, newResourcesParam1()) require.FileExists(t, watchFilePath) require.DirExists(t, watchDirPath) cfg, err := newFileProviderConfig([]string{watchFilePath, watchDirPath}) require.NoError(t, err) pResources := new(message.ProviderResources) - fp, err := New(cfg, pResources) + fp, err := New(ctx, cfg, pResources) require.NoError(t, err) // Start file provider. go func() { - if err := fp.Start(context.Background()); err != nil { + if err := fp.Start(ctx); err != nil { t.Errorf("failed to start file provider: %v", err) } }() @@ -93,104 +113,148 @@ func TestFileProvider(t *testing.T) { // Wait for file provider to be ready. waitFileProviderReady(t) - require.Equal(t, "gateway.envoyproxy.io/gatewayclass-controller", fp.resourcesStore.name) + require.Equal(t, "gateway.envoyproxy.io/gatewayclass-controller", fp.store.name) t.Run("initial resource load", func(t *testing.T) { - require.NotZero(t, pResources.GatewayAPIResources.Len()) - resources := pResources.GetResourcesByGatewayClass("eg") + // Wait for the first reconcile to kick in. + require.Eventually(t, func() bool { + return pResources.GatewayAPIResources.Len() > 0 + }, resourcesUpdateTimeout, resourcesUpdateTick) + resources := pResources.GetResourcesByGatewayClass("eg-1") require.NotNil(t, resources) want := &resource.Resources{} - mustUnmarshal(t, "testdata/resources.all.yaml", want) - - opts := []cmp.Option{ - cmpopts.IgnoreFields(resource.Resources{}, "serviceMap"), - cmpopts.EquateEmpty(), - } - require.Empty(t, cmp.Diff(want, resources, opts...)) + mustUnmarshal(t, "testdata/resources.1.yaml", want) + cmpResources(t, want, resources) }) t.Run("rename the watched file then rename it back", func(t *testing.T) { - // Rename it + // Rename it first, the watched file is losed. renameFilePath := filepath.Join(watchFileBase, "foobar.yaml") err := os.Rename(watchFilePath, renameFilePath) require.NoError(t, err) require.Eventually(t, func() bool { - return pResources.GetResourcesByGatewayClass("eg") == nil + return pResources.GetResourcesByGatewayClass("eg-1") == nil }, resourcesUpdateTimeout, resourcesUpdateTick) - // Rename it back + // Rename it back, the watched file is resumed. err = os.Rename(renameFilePath, watchFilePath) require.NoError(t, err) require.Eventually(t, func() bool { - return pResources.GetResourcesByGatewayClass("eg") != nil + return pResources.GetResourcesByGatewayClass("eg-1") != nil }, resourcesUpdateTimeout, resourcesUpdateTick) - resources := pResources.GetResourcesByGatewayClass("eg") + resources := pResources.GetResourcesByGatewayClass("eg-1") want := &resource.Resources{} - mustUnmarshal(t, "testdata/resources.all.yaml", want) - - opts := []cmp.Option{ - cmpopts.IgnoreFields(resource.Resources{}, "serviceMap"), - cmpopts.EquateEmpty(), - } - require.Empty(t, cmp.Diff(want, resources, opts...)) + mustUnmarshal(t, "testdata/resources.1.yaml", want) + cmpResources(t, want, resources) }) t.Run("remove the watched file", func(t *testing.T) { - err := os.Remove(watchFilePath) - require.NoError(t, err) + require.NoError(t, os.Remove(watchFilePath)) require.Eventually(t, func() bool { - return pResources.GetResourcesByGatewayClass("eg") == nil + return len(pResources.GetResources()) == 0 }, resourcesUpdateTimeout, resourcesUpdateTick) }) - t.Run("add a file in watched dir", func(t *testing.T) { - // Write a new file under watched directory. + t.Run("add a new file in watched dir", func(t *testing.T) { + // Write a new file under empty watched directory. newFilePath := filepath.Join(watchDirPath, "test.yaml") - writeResourcesFile(t, "testdata/resources.tmpl", newFilePath, newDefaultResourcesParam()) + writeResourcesFile(t, newFilePath, newResourcesParam1()) require.Eventually(t, func() bool { - return pResources.GetResourcesByGatewayClass("eg") != nil + return pResources.GetResourcesByGatewayClass("eg-1") != nil }, resourcesUpdateTimeout, resourcesUpdateTick) - resources := pResources.GetResourcesByGatewayClass("eg") + resources := pResources.GetResourcesByGatewayClass("eg-1") want := &resource.Resources{} - mustUnmarshal(t, "testdata/resources.all.yaml", want) + mustUnmarshal(t, "testdata/resources.1.yaml", want) + cmpResources(t, want, resources) + }) - opts := []cmp.Option{ - cmpopts.IgnoreFields(resource.Resources{}, "serviceMap"), - cmpopts.EquateEmpty(), - } - require.Empty(t, cmp.Diff(want, resources, opts...)) + t.Run("rename the file then rename it back in watched dir", func(t *testing.T) { + // Rename it first. + srcFilePath := filepath.Join(watchDirPath, "test.yaml") + dstFilePath := filepath.Join(watchDirPath, "foobar.yaml") + err := os.Rename(srcFilePath, dstFilePath) + require.NoError(t, err) + require.Eventually(t, func() bool { + return pResources.GetResourcesByGatewayClass("eg-1") != nil + }, resourcesUpdateTimeout, resourcesUpdateTick) + + // Rename it back. + err = os.Rename(dstFilePath, srcFilePath) + require.NoError(t, err) + require.Eventually(t, func() bool { + return pResources.GetResourcesByGatewayClass("eg-1") != nil + }, resourcesUpdateTimeout, resourcesUpdateTick) + + resources := pResources.GetResourcesByGatewayClass("eg-1") + want := &resource.Resources{} + mustUnmarshal(t, "testdata/resources.1.yaml", want) + cmpResources(t, want, resources) }) - t.Run("remove a file in watched dir", func(t *testing.T) { + t.Run("update file content in watched dir", func(t *testing.T) { + // Rewrite the file under watched directory. newFilePath := filepath.Join(watchDirPath, "test.yaml") - err := os.Remove(newFilePath) - require.NoError(t, err) + writeResourcesFile(t, newFilePath, newResourcesParam2()) + + require.Eventually(t, func() bool { + return pResources.GetResourcesByGatewayClass("eg-1") == nil && + pResources.GetResourcesByGatewayClass("eg-2") != nil + }, resourcesUpdateTimeout, resourcesUpdateTick) + }) + + t.Run("add another file with new gatewayclass in watched dir", func(t *testing.T) { + // The test.yaml was changed by previous case, safe to use resources param 1 here. + newFilePath := filepath.Join(watchDirPath, "another.yaml") + writeResourcesFile(t, newFilePath, newResourcesParam1()) + require.Eventually(t, func() bool { - return pResources.GetResourcesByGatewayClass("eg") == nil + return pResources.GetResourcesByGatewayClass("eg-1") != nil && + pResources.GetResourcesByGatewayClass("eg-2") != nil + }, resourcesUpdateTimeout, resourcesUpdateTick) + + resources1 := pResources.GetResourcesByGatewayClass("eg-1") + want1 := &resource.Resources{} + mustUnmarshal(t, "testdata/resources.1.yaml", want1) + cmpResources(t, want1, resources1) + + resources2 := pResources.GetResourcesByGatewayClass("eg-2") + want2 := &resource.Resources{} + mustUnmarshal(t, "testdata/resources.2.yaml", want2) + cmpResources(t, want2, resources2) + }) + + t.Run("remove all files in watched dir", func(t *testing.T) { + fp1 := filepath.Join(watchDirPath, "test.yaml") + fp2 := filepath.Join(watchDirPath, "another.yaml") + require.NoError(t, os.Remove(fp1)) + require.NoError(t, os.Remove(fp2)) + require.Eventually(t, func() bool { + return len(pResources.GetResources()) == 0 }, resourcesUpdateTimeout, resourcesUpdateTick) }) t.Cleanup(func() { + cancel() _ = os.RemoveAll(watchFileBase) _ = os.RemoveAll(watchDirPath) }) } -func writeResourcesFile(t *testing.T, tmpl, dst string, params *resourcesParam) { - dstFile, err := os.Create(dst) - require.NoError(t, err) +func writeResourcesFile(t *testing.T, dst string, params *resourcesParam) { + var buf bytes.Buffer // Write parameters into target file. - tmplFile, err := template.ParseFiles(tmpl) + tmplFile, err := template.ParseFiles("testdata/resources.tmpl") require.NoError(t, err) - err = tmplFile.Execute(dstFile, params) + err = tmplFile.Execute(&buf, params) require.NoError(t, err) - require.NoError(t, dstFile.Close()) + // Write file in an atomic way, prevent unnecessary reconcile. + require.NoError(t, os.WriteFile(dst, buf.Bytes(), 0o600)) } func waitFileProviderReady(t *testing.T) { @@ -223,3 +287,12 @@ func mustUnmarshal(t *testing.T, path string, out interface{}) { require.NoError(t, err) require.NoError(t, yaml.UnmarshalStrict(content, out, yaml.DisallowUnknownFields)) } + +func cmpResources(t *testing.T, x, y interface{}) { + opts := []cmp.Option{ + cmpopts.IgnoreFields(resource.Resources{}, "serviceMap"), + cmpopts.IgnoreFields(metav1.ObjectMeta{}, "ResourceVersion"), + cmpopts.EquateEmpty(), + } + require.Empty(t, cmp.Diff(x, y, opts...)) +} diff --git a/internal/provider/file/status.go b/internal/provider/file/status.go index 83d86086bd..0e50c5fbb2 100644 --- a/internal/provider/file/status.go +++ b/internal/provider/file/status.go @@ -8,287 +8,99 @@ package file import ( "context" "fmt" + "sync" - "k8s.io/apimachinery/pkg/types" - gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" - gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/yaml" - egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" - "github.com/envoyproxy/gateway/internal/gatewayapi/resource" - "github.com/envoyproxy/gateway/internal/gatewayapi/status" - "github.com/envoyproxy/gateway/internal/message" + "github.com/envoyproxy/gateway/internal/provider/kubernetes" ) -func (p *Provider) subscribeAndUpdateStatus(ctx context.Context) { - // TODO: trigger gatewayclass status update in file-provider - // GatewayClass object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "gatewayclass-status"}, - p.resourcesStore.resources.GatewayClassStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1.GatewayClassStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } - - p.logStatus(*update.Value, resource.KindGateway) - }, - ) - p.logger.Info("gatewayClass status subscriber shutting down") - }() - - // Gateway object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "gateway-status"}, - p.resourcesStore.resources.GatewayStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1.GatewayStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } - - // Update Gateway conditions, ignore addresses - gtw := new(gwapiv1.Gateway) - gtw.Status = *update.Value - status.UpdateGatewayStatusAccepted(gtw) - - p.logStatus(gtw.Status, resource.KindGateway) - }, - ) - p.logger.Info("gateway status subscriber shutting down") - }() - - // HTTPRoute object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "httproute-status"}, - p.resourcesStore.resources.HTTPRouteStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1.HTTPRouteStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } - - p.logStatus(*update.Value, resource.KindHTTPRoute) - }, - ) - p.logger.Info("httpRoute status subscriber shutting down") - }() - - // GRPCRoute object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "grpcroute-status"}, - p.resourcesStore.resources.GRPCRouteStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1.GRPCRouteStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } - - p.logStatus(*update.Value, resource.KindGRPCRoute) - }, - ) - p.logger.Info("grpcRoute status subscriber shutting down") - }() - - // TLSRoute object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "tlsroute-status"}, - p.resourcesStore.resources.TLSRouteStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1a2.TLSRouteStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } - - p.logStatus(*update.Value, resource.KindTLSRoute) - }, - ) - p.logger.Info("tlsRoute status subscriber shutting down") - }() - - // TCPRoute object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "tcproute-status"}, - p.resourcesStore.resources.TCPRouteStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1a2.TCPRouteStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } - - p.logStatus(*update.Value, resource.KindTCPRoute) - }, - ) - p.logger.Info("tcpRoute status subscriber shutting down") - }() - - // UDPRoute object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "udproute-status"}, - p.resourcesStore.resources.UDPRouteStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1a2.UDPRouteStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } - - p.logStatus(*update.Value, resource.KindUDPRoute) - }, - ) - p.logger.Info("udpRoute status subscriber shutting down") - }() - - // EnvoyPatchPolicy object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "envoypatchpolicy-status"}, - p.resourcesStore.resources.EnvoyPatchPolicyStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1a2.PolicyStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } - - p.logStatus(*update.Value, resource.KindEnvoyPatchPolicy) - }, - ) - p.logger.Info("envoyPatchPolicy status subscriber shutting down") - }() - - // ClientTrafficPolicy object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "clienttrafficpolicy-status"}, - p.resourcesStore.resources.ClientTrafficPolicyStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1a2.PolicyStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } - - p.logStatus(*update.Value, resource.KindClientTrafficPolicy) - }, - ) - p.logger.Info("clientTrafficPolicy status subscriber shutting down") - }() - - // BackendTrafficPolicy object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "backendtrafficpolicy-status"}, - p.resourcesStore.resources.BackendTrafficPolicyStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1a2.PolicyStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } - - p.logStatus(*update.Value, resource.KindBackendTrafficPolicy) - }, - ) - p.logger.Info("backendTrafficPolicy status subscriber shutting down") - }() +type StatusHandler struct { + logger logr.Logger + updateChannel chan kubernetes.Update + wg *sync.WaitGroup +} - // SecurityPolicy object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "securitypolicy-status"}, - p.resourcesStore.resources.SecurityPolicyStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1a2.PolicyStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } +func NewStatusHandler(log logr.Logger) *StatusHandler { + u := &StatusHandler{ + logger: log, + updateChannel: make(chan kubernetes.Update, 1000), + wg: new(sync.WaitGroup), + } - p.logStatus(*update.Value, resource.KindSecurityPolicy) - }, - ) - p.logger.Info("securityPolicy status subscriber shutting down") - }() + u.wg.Add(1) - // BackendTLSPolicy object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "backendtlspolicy-status"}, - p.resourcesStore.resources.BackendTLSPolicyStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1a2.PolicyStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } + return u +} - p.logStatus(*update.Value, resource.KindBackendTLSPolicy) - }, - ) - p.logger.Info("backendTLSPolicy status subscriber shutting down") - }() +// Start runs the goroutine to perform status writes. +func (u *StatusHandler) Start(ctx context.Context, ready *sync.WaitGroup) { + u.logger.Info("started status update handler") + defer u.logger.Info("stopped status update handler") + + // Enable Updaters to start sending updates to this handler. + u.wg.Done() + ready.Done() + + for { + select { + case <-ctx.Done(): + return + case update := <-u.updateChannel: + u.logger.Info("received a status update", "namespace", update.NamespacedName.Namespace, + "name", update.NamespacedName.Name) + + u.logStatus(update) + } + } +} - // EnvoyExtensionPolicy object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "envoyextensionpolicy-status"}, - p.resourcesStore.resources.EnvoyExtensionPolicyStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *gwapiv1a2.PolicyStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } +func (u *StatusHandler) logStatus(update kubernetes.Update) { + obj := update.Resource + newObj := update.Mutator.Mutate(obj) + log := u.logger.WithValues("key", update.NamespacedName.String()) - p.logStatus(*update.Value, resource.KindEnvoyExtensionPolicy) - }, - ) - p.logger.Info("envoyExtensionPolicy status subscriber shutting down") - }() + // Log the resource status. + raw, err := runtime.DefaultUnstructuredConverter.ToUnstructured(newObj) + if err != nil { + log.Error(err, "failed to convert object") + return + } - // Backend object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "backend-status"}, - p.resourcesStore.resources.BackendStatuses.Subscribe(ctx), - func(update message.Update[types.NamespacedName, *egv1a1.BackendStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } + rawStatus, ok := raw["status"] + if !ok { + log.Error(fmt.Errorf("no status field"), "failed to log status") + return + } - p.logStatus(*update.Value, resource.KindBackend) - }, - ) - p.logger.Info("backend status subscriber shutting down") - }() + byteStatus, err := yaml.Marshal(rawStatus) + if err != nil { + log.Error(err, "failed to marshal object") + return + } - if p.extensionManagerEnabled { - // ExtensionServerPolicy object status updater - go func() { - message.HandleSubscription( - message.Metadata{Runner: string(egv1a1.LogComponentProviderRunner), Message: "extensionserverpolicies-status"}, - p.resourcesStore.resources.ExtensionPolicyStatuses.Subscribe(ctx), - func(update message.Update[message.NamespacedNameAndGVK, *gwapiv1a2.PolicyStatus], errChan chan error) { - // skip delete updates. - if update.Delete { - return - } + log.Info(fmt.Sprintf("Got new status for %s\n%s", kubernetes.KindOf(obj), string(byteStatus))) +} - p.logStatus(*update.Value, "ExtensionServerPolicy") - }, - ) - p.logger.Info("extensionServerPolicies status subscriber shutting down") - }() +// Writer retrieves the interface that should be used to write to the StatusHandler. +func (u *StatusHandler) Writer() kubernetes.Updater { + return &StatusWriter{ + updateChannel: u.updateChannel, + wg: u.wg, } } -func (p *Provider) logStatus(obj interface{}, statusType string) { - if status, err := yaml.Marshal(obj); err == nil { - p.logger.Info(fmt.Sprintf("Got new status for %s \n%s", statusType, string(status))) - } else { - p.logger.Error(err, "failed to log status", "type", statusType) - } +// StatusWriter takes status updates and sends these to the StatusHandler via a channel. +type StatusWriter struct { + updateChannel chan<- kubernetes.Update + wg *sync.WaitGroup +} + +// Send sends the given Update off to the update channel for writing by the StatusHandler. +func (u *StatusWriter) Send(update kubernetes.Update) { + // Wait until updater is ready + u.wg.Wait() + u.updateChannel <- update } diff --git a/internal/provider/file/store.go b/internal/provider/file/store.go index 448f1807cf..cf70c78b28 100644 --- a/internal/provider/file/store.go +++ b/internal/provider/file/store.go @@ -6,68 +6,349 @@ package file import ( + "context" + "crypto/rand" + "errors" + "fmt" + "math" + "math/big" + "reflect" + "sort" + "time" + "github.com/go-logr/logr" + kerrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/controller-runtime/pkg/client" "github.com/envoyproxy/gateway/internal/gatewayapi/resource" "github.com/envoyproxy/gateway/internal/message" ) +const ( + GatewayDeletionOrder = 3 +) + type resourcesStore struct { name string + keys sets.Set[storeKey] + client client.Client resources *message.ProviderResources + reconcile chan int64 logger logr.Logger } -func newResourcesStore(name string, resources *message.ProviderResources, logger logr.Logger) *resourcesStore { +type storeKey struct { + schema.GroupVersionKind + types.NamespacedName + + // deletionOrder is used to determine the order in which a resource is deleted. + // The larger the value, the earlier it is deleted. + deletionOrder int +} + +func (s storeKey) String() string { + return fmt.Sprintf("%s/%s/%d", + s.GroupVersionKind.String(), s.NamespacedName.String(), s.deletionOrder) +} + +func newResourcesStore(name string, client client.Client, resources *message.ProviderResources, logger logr.Logger) *resourcesStore { return &resourcesStore{ name: name, + keys: sets.New[storeKey](), + client: client, resources: resources, + reconcile: make(chan int64), logger: logger, } } -// HandleEvent simply removes all the resources and triggers a resources reload from files -// and directories despite of the event type. -// TODO: Enhance this method by respecting the event type, and add support for multiple GatewayClass. -func (r *resourcesStore) HandleEvent(files, dirs []string) { - r.logger.Info("reload all resources") - - r.resources.GatewayAPIResources.Delete(r.name) - if err := r.LoadAndStore(files, dirs); err != nil { - r.logger.Error(err, "failed to load and store resources") +func newStoreKey(obj client.Object) storeKey { + return storeKey{ + GroupVersionKind: obj.GetObjectKind().GroupVersionKind(), + NamespacedName: client.ObjectKeyFromObject(obj), } } -// LoadAndStore loads and stores all resources from files and directories. -func (r *resourcesStore) LoadAndStore(files, dirs []string) error { +// ReloadAll loads and stores all resources from all given files and directories. +func (r *resourcesStore) ReloadAll(ctx context.Context, files, dirs []string) error { + // TODO(sh2): add arbitrary number of resources support for load function. resources, err := loadFromFilesAndDirs(files, dirs) if err != nil { return err } - // TODO(sh2): For now, we assume that one file only contains one GatewayClass and all its other - // related resources, like Gateway, HTTPRoute, etc. If we managed to extend Resources structure, - // we also need to process all the resources and its relationship, like what is done in - // Kubernetes provider. However, this will cause us to maintain two places of the same logic - // in each provider. The ideal case is two different providers share the same resources process logic. - // - // - This issue is tracked by https://github.com/envoyproxy/gateway/issues/3213 - - // We cannot make sure by the time the Write event was triggered, whether the GatewayClass exist, - // so here we just simply Store the first gatewayapi.Resources that has GatewayClass. - gwcResources := make(resource.ControllerResources, 0, 1) + var errList error + currentKeys := sets.New[storeKey]() for _, res := range resources { - if res.GatewayClass != nil { - gwcResources = append(gwcResources, res) + collectKeys, err := r.storeResources(ctx, res) + if err != nil { + errList = errors.Join(errList, err) + } + currentKeys = currentKeys.Union(collectKeys) + } + + // If no resources were created or updated, stop reconciling. + if errList != nil && len(currentKeys) == 0 { + return errList + } + + // Remove the resources that no longer exist. + rn := 0 + deletedKeys := r.keys.Difference(currentKeys) + for _, k := range deletionOrderKeyList(deletedKeys) { + delObj := makeUnstructuredObjectFromKey(k) + if err := r.client.Delete(ctx, delObj); err != nil { + errList = errors.Join(errList, err) + // Insert back if the object is not be removed. + currentKeys.Insert(k) + } else if k.deletionOrder <= GatewayDeletionOrder { + // Reconcile once if gateway got deleted, this may be able to + // remove the finalizer on gatewayclass. + r.reconcile <- generateReconcileID() + rn++ } } - if len(gwcResources) == 0 { - return nil + + r.keys = currentKeys + r.reconcile <- generateReconcileID() + rn++ + + r.logger.Info("reload resources finished", + "reload_resources_num", len(r.keys), "reconcile_times", rn, "time", time.Now()) + return errList +} + +// storeResources stores resources via offline gateway-api client. +// For file provider, all gateway-api resources will be stored except: +// - Service +// - ServiceImport +// - EndpointSlices +// Becasues these resources has no effects on the host infra layer. +func (r *resourcesStore) storeResources(ctx context.Context, re *resource.Resources) (sets.Set[storeKey], error) { + if re == nil { + return nil, nil + } + + var ( + errs error + collectKeys = sets.New[storeKey]() + ) + + if err := r.stroeObjectWithKeys(ctx, re.EnvoyProxyForGatewayClass, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + + if err := r.stroeObjectWithKeys(ctx, re.GatewayClass, collectKeys); err != nil { + errs = errors.Join(errs, err) } - r.resources.GatewayAPIResources.Store(r.name, &gwcResources) - r.logger.Info("loaded and stored resources successfully") + for _, obj := range re.EnvoyProxiesForGateways { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.Gateways { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.HTTPRoutes { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.GRPCRoutes { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.TLSRoutes { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.TCPRoutes { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.UDPRoutes { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.ReferenceGrants { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.Namespaces { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.Secrets { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.ConfigMaps { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.EnvoyPatchPolicies { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.ClientTrafficPolicies { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.BackendTrafficPolicies { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.SecurityPolicies { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.BackendTLSPolicies { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.EnvoyExtensionPolicies { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.Backends { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + for _, obj := range re.HTTPRouteFilters { + if err := r.stroeObjectWithKeys(ctx, obj, collectKeys); err != nil { + errs = errors.Join(errs, err) + } + } + + return collectKeys, errs +} + +// stroeObjectWithKeys stores object while collecting its key. +func (r *resourcesStore) stroeObjectWithKeys(ctx context.Context, obj client.Object, keys sets.Set[storeKey]) error { + key, err := r.storeObject(ctx, obj) + if err != nil && key != nil { + return fmt.Errorf("failed to store %s %s: %w", key.Kind, key.NamespacedName.String(), err) + } else if err != nil { + return fmt.Errorf("failed to store object: %w", err) + } + + if key != nil { + keys.Insert(*key) + } return nil } + +// storeObject will do create for non-exist object and update for existing object. +func (r *resourcesStore) storeObject(ctx context.Context, obj client.Object) (*storeKey, error) { + if obj == nil || reflect.ValueOf(obj).IsNil() { + return nil, nil + } + + var ( + err error + key = newStoreKey(obj) + oldObj = makeUnstructuredObjectFromKey(key) + ) + + if err = r.client.Get(ctx, key.NamespacedName, oldObj); err == nil { + return &key, r.client.Patch(ctx, obj, client.Merge) + } + if kerrors.IsNotFound(err) { + return &key, r.client.Create(ctx, obj) + } + + return nil, err +} + +func makeUnstructuredObjectFromKey(key storeKey) *unstructured.Unstructured { + obj := &unstructured.Unstructured{} + obj.SetGroupVersionKind(key.GroupVersionKind) + obj.SetNamespace(key.Namespace) + obj.SetName(key.Name) + return obj +} + +// deletionOrderKeyList returns a list sorted in descending order by deletionOrder in its key. +func deletionOrderKeyList(keys sets.Set[storeKey]) []storeKey { + out := keys.UnsortedList() + for i, k := range out { + switch k.Kind { + case resource.KindNamespace, resource.KindReferenceGrant, + resource.KindConfigMap, resource.KindSecret: + out[i].deletionOrder = GatewayDeletionOrder - 3 + + case resource.KindEnvoyProxy: + out[i].deletionOrder = GatewayDeletionOrder - 2 + + case resource.KindGatewayClass: + out[i].deletionOrder = GatewayDeletionOrder - 1 + + case resource.KindGateway: + out[i].deletionOrder = GatewayDeletionOrder + + case resource.KindHTTPRoute, resource.KindGRPCRoute, + resource.KindTLSRoute, resource.KindTCPRoute, resource.KindUDPRoute, + resource.KindSecurityPolicy, resource.KindClientTrafficPolicy, resource.KindBackendTrafficPolicy, + resource.KindEnvoyPatchPolicy, resource.KindEnvoyExtensionPolicy, resource.KindBackendTLSPolicy: + out[i].deletionOrder = GatewayDeletionOrder + 1 + + case resource.KindBackend, resource.KindHTTPRouteFilter: + out[i].deletionOrder = GatewayDeletionOrder + 2 + + default: + out[i].deletionOrder = GatewayDeletionOrder + 3 + } + } + + sort.Slice(out, func(i, j int) bool { + return out[i].deletionOrder > out[j].deletionOrder + }) + return out +} + +func generateReconcileID() int64 { + n, _ := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) + return n.Int64() +} diff --git a/internal/provider/file/testdata/resources.all.yaml b/internal/provider/file/testdata/resources.1.yaml similarity index 83% rename from internal/provider/file/testdata/resources.all.yaml rename to internal/provider/file/testdata/resources.1.yaml index 989ae8025a..fef92caaa7 100644 --- a/internal/provider/file/testdata/resources.all.yaml +++ b/internal/provider/file/testdata/resources.1.yaml @@ -3,21 +3,23 @@ backends: apiVersion: gateway.envoyproxy.io/v1alpha1 metadata: creationTimestamp: null - name: backend + name: backend-1 namespace: envoy-gateway-system spec: type: Endpoints endpoints: - ip: address: 0.0.0.0 - port: 3000 + port: 3001 status: {} gatewayClass: kind: GatewayClass apiVersion: gateway.networking.k8s.io/v1 metadata: creationTimestamp: null - name: eg + name: eg-1 + finalizers: + - gateway-exists-finalizer.gateway.networking.k8s.io spec: controllerName: gateway.envoyproxy.io/gatewayclass-controller status: {} @@ -26,16 +28,16 @@ gateways: apiVersion: gateway.networking.k8s.io/v1 metadata: creationTimestamp: null - name: eg + name: eg-1 namespace: envoy-gateway-system spec: - gatewayClassName: eg + gatewayClassName: eg-1 listeners: - allowedRoutes: namespaces: from: Same name: http - port: 8888 + port: 8801 protocol: HTTP status: {} httpRoutes: @@ -43,20 +45,20 @@ httpRoutes: apiVersion: gateway.networking.k8s.io/v1 metadata: creationTimestamp: null - name: backend + name: backend-1 namespace: envoy-gateway-system spec: hostnames: - - www.example.com + - www.test1.com parentRefs: - group: gateway.networking.k8s.io kind: Gateway - name: eg + name: eg-1 rules: - backendRefs: - group: gateway.envoyproxy.io kind: Backend - name: backend + name: backend-1 weight: 1 matches: - path: diff --git a/internal/provider/file/testdata/resources.2.yaml b/internal/provider/file/testdata/resources.2.yaml new file mode 100644 index 0000000000..938a7845b4 --- /dev/null +++ b/internal/provider/file/testdata/resources.2.yaml @@ -0,0 +1,76 @@ +backends: +- kind: Backend + apiVersion: gateway.envoyproxy.io/v1alpha1 + metadata: + creationTimestamp: null + name: backend-2 + namespace: envoy-gateway-system + spec: + type: Endpoints + endpoints: + - ip: + address: 0.0.0.0 + port: 3002 + status: {} +gatewayClass: + kind: GatewayClass + apiVersion: gateway.networking.k8s.io/v1 + metadata: + creationTimestamp: null + name: eg-2 + finalizers: + - gateway-exists-finalizer.gateway.networking.k8s.io + spec: + controllerName: gateway.envoyproxy.io/gatewayclass-controller + status: {} +gateways: +- kind: Gateway + apiVersion: gateway.networking.k8s.io/v1 + metadata: + creationTimestamp: null + name: eg-2 + namespace: envoy-gateway-system + spec: + gatewayClassName: eg-2 + listeners: + - allowedRoutes: + namespaces: + from: Same + name: http + port: 8802 + protocol: HTTP + status: {} +httpRoutes: +- kind: HTTPRoute + apiVersion: gateway.networking.k8s.io/v1 + metadata: + creationTimestamp: null + name: backend-2 + namespace: envoy-gateway-system + spec: + hostnames: + - www.test2.com + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: eg-2 + rules: + - backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend-2 + weight: 1 + matches: + - path: + type: PathPrefix + value: / + status: + parents: null +namespaces: +- kind: Namespace + apiVersion: v1 + metadata: + creationTimestamp: null + name: envoy-gateway-system + spec: {} + status: {} diff --git a/internal/provider/file/testdata/resources.tmpl b/internal/provider/file/testdata/resources.tmpl index f34bf1e0c3..d472be52b0 100644 --- a/internal/provider/file/testdata/resources.tmpl +++ b/internal/provider/file/testdata/resources.tmpl @@ -24,7 +24,7 @@ spec: parentRefs: - name: {{.GatewayName}} hostnames: - - "www.example.com" + - {{.HTTPRouteHostname}} rules: - backendRefs: - group: "gateway.envoyproxy.io" @@ -43,4 +43,4 @@ spec: endpoints: - ip: address: 0.0.0.0 - port: 3000 + port: {{.EndpointPort}} diff --git a/internal/provider/kubernetes/controller_offline.go b/internal/provider/kubernetes/controller_offline.go new file mode 100644 index 0000000000..e6d96bbf9f --- /dev/null +++ b/internal/provider/kubernetes/controller_offline.go @@ -0,0 +1,131 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package kubernetes + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" + gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwapiv1a3 "sigs.k8s.io/gateway-api/apis/v1alpha3" + gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/envoyproxy/gateway/internal/envoygateway" + "github.com/envoyproxy/gateway/internal/envoygateway/config" + "github.com/envoyproxy/gateway/internal/message" +) + +// OfflineGatewayAPIReconciler can be used for non-kuberetes provider. +// It can let other providers to have the same reconcile logic without rely on apiserver. +type OfflineGatewayAPIReconciler struct { + gatewayAPIReconciler + + Client client.Client +} + +func NewOfflineGatewayAPIController( + ctx context.Context, cfg *config.Server, su Updater, resources *message.ProviderResources, +) (*OfflineGatewayAPIReconciler, error) { + if cfg == nil || resources == nil { + return nil, fmt.Errorf("missing config or resources that offline controller requires") + } + + // Check provider type. + if cfg.EnvoyGateway.Provider.Type == egv1a1.ProviderTypeKubernetes { + return nil, fmt.Errorf("offline controller cannot work with kubernetes provider") + } + + // Gather additional resources to watch from registered extensions. + var ( + extGVKs []schema.GroupVersionKind + extServerPoliciesGVKs []schema.GroupVersionKind + ) + + if cfg.EnvoyGateway.ExtensionManager != nil { + for _, rsrc := range cfg.EnvoyGateway.ExtensionManager.Resources { + gvk := schema.GroupVersionKind(rsrc) + extGVKs = append(extGVKs, gvk) + } + for _, rsrc := range cfg.EnvoyGateway.ExtensionManager.PolicyResources { + gvk := schema.GroupVersionKind(rsrc) + extServerPoliciesGVKs = append(extServerPoliciesGVKs, gvk) + } + } + + cli := newOfflineGatewayAPIClient() + r := gatewayAPIReconciler{ + client: cli, + log: cfg.Logger, + classController: gwapiv1.GatewayController(cfg.EnvoyGateway.Gateway.ControllerName), + namespace: cfg.ControllerNamespace, + statusUpdater: su, + resources: resources, + extGVKs: extGVKs, + store: newProviderStore(), + envoyGateway: cfg.EnvoyGateway, + mergeGateways: sets.New[string](), + extServerPolicies: extServerPoliciesGVKs, + } + + r.log.Info("created offline gatewayapi controller") + if su != nil { + r.subscribeAndUpdateStatus(ctx, cfg.EnvoyGateway.ExtensionManager != nil) + } + + return &OfflineGatewayAPIReconciler{ + gatewayAPIReconciler: r, + Client: cli, + }, nil +} + +// Reconcile calls reconcile method in gateway-api controller, this method +// should be called manually. +func (r *OfflineGatewayAPIReconciler) Reconcile(ctx context.Context) error { + _, err := r.gatewayAPIReconciler.Reconcile(ctx, reconcile.Request{}) + return err +} + +// newOfflineGatewayAPIClient returns a offline client with gateway-api schemas and indexes. +func newOfflineGatewayAPIClient() client.Client { + return fake.NewClientBuilder(). + WithScheme(envoygateway.GetScheme()). + WithIndex(&gwapiv1.Gateway{}, classGatewayIndex, gatewayIndexFunc). + WithIndex(&gwapiv1.Gateway{}, secretGatewayIndex, secretGatewayIndexFunc). + WithIndex(&gwapiv1.HTTPRoute{}, gatewayHTTPRouteIndex, gatewayHTTPRouteIndexFunc). + WithIndex(&gwapiv1.HTTPRoute{}, backendHTTPRouteIndex, backendHTTPRouteIndexFunc). + WithIndex(&gwapiv1.HTTPRoute{}, httpRouteFilterHTTPRouteIndex, httpRouteFilterHTTPRouteIndexFunc). + WithIndex(&gwapiv1.GRPCRoute{}, gatewayGRPCRouteIndex, gatewayGRPCRouteIndexFunc). + WithIndex(&gwapiv1.GRPCRoute{}, backendGRPCRouteIndex, backendGRPCRouteIndexFunc). + WithIndex(&gwapiv1a2.TCPRoute{}, gatewayTCPRouteIndex, gatewayTCPRouteIndexFunc). + WithIndex(&gwapiv1a2.TCPRoute{}, backendTCPRouteIndex, backendTCPRouteIndexFunc). + WithIndex(&gwapiv1a2.UDPRoute{}, gatewayUDPRouteIndex, gatewayUDPRouteIndexFunc). + WithIndex(&gwapiv1a2.UDPRoute{}, backendUDPRouteIndex, backendUDPRouteIndexFunc). + WithIndex(&gwapiv1a2.TLSRoute{}, gatewayTLSRouteIndex, gatewayTLSRouteIndexFunc). + WithIndex(&gwapiv1a2.TLSRoute{}, backendTLSRouteIndex, backendTLSRouteIndexFunc). + WithIndex(&egv1a1.EnvoyProxy{}, backendEnvoyProxyTelemetryIndex, backendEnvoyProxyTelemetryIndexFunc). + WithIndex(&egv1a1.EnvoyProxy{}, secretEnvoyProxyIndex, secretEnvoyProxyIndexFunc). + WithIndex(&egv1a1.BackendTrafficPolicy{}, configMapBtpIndex, configMapBtpIndexFunc). + WithIndex(&egv1a1.ClientTrafficPolicy{}, configMapCtpIndex, configMapCtpIndexFunc). + WithIndex(&egv1a1.ClientTrafficPolicy{}, secretCtpIndex, secretCtpIndexFunc). + WithIndex(&egv1a1.SecurityPolicy{}, secretSecurityPolicyIndex, secretSecurityPolicyIndexFunc). + WithIndex(&egv1a1.SecurityPolicy{}, backendSecurityPolicyIndex, backendSecurityPolicyIndexFunc). + WithIndex(&egv1a1.SecurityPolicy{}, configMapSecurityPolicyIndex, configMapSecurityPolicyIndexFunc). + WithIndex(&egv1a1.EnvoyExtensionPolicy{}, backendEnvoyExtensionPolicyIndex, backendEnvoyExtensionPolicyIndexFunc). + WithIndex(&egv1a1.EnvoyExtensionPolicy{}, secretEnvoyExtensionPolicyIndex, secretEnvoyExtensionPolicyIndexFunc). + WithIndex(&gwapiv1a3.BackendTLSPolicy{}, configMapBtlsIndex, configMapBtlsIndexFunc). + WithIndex(&gwapiv1a3.BackendTLSPolicy{}, secretBtlsIndex, secretBtlsIndexFunc). + WithIndex(&egv1a1.HTTPRouteFilter{}, configMapHTTPRouteFilterIndex, configMapRouteFilterIndexFunc). + WithIndex(&egv1a1.HTTPRouteFilter{}, secretHTTPRouteFilterIndex, secretRouteFilterIndexFunc). + WithIndex(&gwapiv1b1.ReferenceGrant{}, targetRefGrantRouteIndex, getReferenceGrantIndexerFunc). + Build() +} diff --git a/internal/provider/kubernetes/controller_offline_test.go b/internal/provider/kubernetes/controller_offline_test.go new file mode 100644 index 0000000000..a8b3296a26 --- /dev/null +++ b/internal/provider/kubernetes/controller_offline_test.go @@ -0,0 +1,49 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package kubernetes + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/require" + + egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/envoyproxy/gateway/internal/envoygateway/config" + "github.com/envoyproxy/gateway/internal/message" +) + +func TestNewOfflineGatewayAPIController(t *testing.T) { + t.Run("offline controller requires config and resources", func(t *testing.T) { + _, err := NewOfflineGatewayAPIController(context.Background(), nil, nil, nil) + require.Error(t, err) + }) + + t.Run("offline controller does not support k8s provider type", func(t *testing.T) { + cfg, err := config.New(os.Stdout) + require.NoError(t, err) + + cfg.EnvoyGateway.Provider = &egv1a1.EnvoyGatewayProvider{ + Type: egv1a1.ProviderTypeKubernetes, + } + pResources := new(message.ProviderResources) + _, err = NewOfflineGatewayAPIController(context.Background(), cfg, nil, pResources) + require.Error(t, err) + }) + + t.Run("offline controller creation success", func(t *testing.T) { + cfg, err := config.New(os.Stdout) + require.NoError(t, err) + + cfg.EnvoyGateway.Provider = &egv1a1.EnvoyGatewayProvider{ + Type: egv1a1.ProviderTypeCustom, + } + pResources := new(message.ProviderResources) + _, err = NewOfflineGatewayAPIController(context.Background(), cfg, nil, pResources) + require.NoError(t, err) + }) +} diff --git a/internal/provider/kubernetes/controller_test.go b/internal/provider/kubernetes/controller_test.go index b2d6cdaf79..4ac85754f1 100644 --- a/internal/provider/kubernetes/controller_test.go +++ b/internal/provider/kubernetes/controller_test.go @@ -434,7 +434,7 @@ func TestProcessEnvoyExtensionPolicyObjectRefs(t *testing.T) { r.client = fakeclient.NewClientBuilder(). WithScheme(envoygateway.GetScheme()). WithObjects(objs...). - WithIndex(&gwapiv1b1.ReferenceGrant{}, targetRefGrantRouteIndex, getReferenceGrantIndexerFunc()). + WithIndex(&gwapiv1b1.ReferenceGrant{}, targetRefGrantRouteIndex, getReferenceGrantIndexerFunc). Build() resourceTree := resource.NewResources() diff --git a/internal/provider/kubernetes/indexers.go b/internal/provider/kubernetes/indexers.go index cd43fc34ba..5114ce5882 100644 --- a/internal/provider/kubernetes/indexers.go +++ b/internal/provider/kubernetes/indexers.go @@ -57,21 +57,19 @@ const ( ) func addReferenceGrantIndexers(ctx context.Context, mgr manager.Manager) error { - if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1b1.ReferenceGrant{}, targetRefGrantRouteIndex, getReferenceGrantIndexerFunc()); err != nil { + if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1b1.ReferenceGrant{}, targetRefGrantRouteIndex, getReferenceGrantIndexerFunc); err != nil { return err } return nil } -func getReferenceGrantIndexerFunc() func(rawObj client.Object) []string { - return func(rawObj client.Object) []string { - refGrant := rawObj.(*gwapiv1b1.ReferenceGrant) - var referredServices []string - for _, target := range refGrant.Spec.To { - referredServices = append(referredServices, string(target.Kind)) - } - return referredServices +func getReferenceGrantIndexerFunc(rawObj client.Object) []string { + refGrant := rawObj.(*gwapiv1b1.ReferenceGrant) + var referredServices []string + for _, target := range refGrant.Spec.To { + referredServices = append(referredServices, string(target.Kind)) } + return referredServices } // addHTTPRouteIndexers adds indexing on HTTPRoute. @@ -176,23 +174,6 @@ func httpRouteFilterHTTPRouteIndexFunc(rawObj client.Object) []string { return refs } -func secretEnvoyProxyIndexFunc(rawObj client.Object) []string { - ep := rawObj.(*egv1a1.EnvoyProxy) - var secretReferences []string - if ep.Spec.BackendTLS != nil { - if ep.Spec.BackendTLS.ClientCertificateRef != nil { - if *ep.Spec.BackendTLS.ClientCertificateRef.Kind == resource.KindSecret { - secretReferences = append(secretReferences, - types.NamespacedName{ - Namespace: gatewayapi.NamespaceDerefOr(ep.Spec.BackendTLS.ClientCertificateRef.Namespace, ep.Namespace), - Name: string(ep.Spec.BackendTLS.ClientCertificateRef.Name), - }.String()) - } - } - } - return secretReferences -} - func addEnvoyProxyIndexers(ctx context.Context, mgr manager.Manager) error { if err := mgr.GetFieldIndexer().IndexField(ctx, &egv1a1.EnvoyProxy{}, backendEnvoyProxyTelemetryIndex, backendEnvoyProxyTelemetryIndexFunc); err != nil { return err @@ -216,6 +197,23 @@ func backendEnvoyProxyTelemetryIndexFunc(rawObj client.Object) []string { return refs.UnsortedList() } +func secretEnvoyProxyIndexFunc(rawObj client.Object) []string { + ep := rawObj.(*egv1a1.EnvoyProxy) + var secretReferences []string + if ep.Spec.BackendTLS != nil { + if ep.Spec.BackendTLS.ClientCertificateRef != nil { + if *ep.Spec.BackendTLS.ClientCertificateRef.Kind == resource.KindSecret { + secretReferences = append(secretReferences, + types.NamespacedName{ + Namespace: gatewayapi.NamespaceDerefOr(ep.Spec.BackendTLS.ClientCertificateRef.Namespace, ep.Namespace), + Name: string(ep.Spec.BackendTLS.ClientCertificateRef.Name), + }.String()) + } + } + } + return secretReferences +} + func accessLogRefs(ep *egv1a1.EnvoyProxy) []string { var refs []string @@ -353,23 +351,7 @@ func backendGRPCRouteIndexFunc(rawObj client.Object) []string { // referenced in TLSRoute objects via `.spec.rules.backendRefs`. This helps in // querying for TLSRoutes that are affected by a particular Service CRUD. func addTLSRouteIndexers(ctx context.Context, mgr manager.Manager) error { - if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1a2.TLSRoute{}, gatewayTLSRouteIndex, func(rawObj client.Object) []string { - tlsRoute := rawObj.(*gwapiv1a2.TLSRoute) - var gateways []string - for _, parent := range tlsRoute.Spec.ParentRefs { - if string(*parent.Kind) == resource.KindGateway { - // If an explicit Gateway namespace is not provided, use the TLSRoute namespace to - // lookup the provided Gateway Name. - gateways = append(gateways, - types.NamespacedName{ - Namespace: gatewayapi.NamespaceDerefOr(parent.Namespace, tlsRoute.Namespace), - Name: string(parent.Name), - }.String(), - ) - } - } - return gateways - }); err != nil { + if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1a2.TLSRoute{}, gatewayTLSRouteIndex, gatewayTLSRouteIndexFunc); err != nil { return err } @@ -379,6 +361,24 @@ func addTLSRouteIndexers(ctx context.Context, mgr manager.Manager) error { return nil } +func gatewayTLSRouteIndexFunc(rawObj client.Object) []string { + tlsRoute := rawObj.(*gwapiv1a2.TLSRoute) + var gateways []string + for _, parent := range tlsRoute.Spec.ParentRefs { + if string(*parent.Kind) == resource.KindGateway { + // If an explicit Gateway namespace is not provided, use the TLSRoute namespace to + // lookup the provided Gateway Name. + gateways = append(gateways, + types.NamespacedName{ + Namespace: gatewayapi.NamespaceDerefOr(parent.Namespace, tlsRoute.Namespace), + Name: string(parent.Name), + }.String(), + ) + } + } + return gateways +} + func backendTLSRouteIndexFunc(rawObj client.Object) []string { tlsroute := rawObj.(*gwapiv1a2.TLSRoute) var backendRefs []string @@ -403,23 +403,7 @@ func backendTLSRouteIndexFunc(rawObj client.Object) []string { // referenced in TCPRoute objects via `.spec.rules.backendRefs`. This helps in // querying for TCPRoutes that are affected by a particular Service CRUD. func addTCPRouteIndexers(ctx context.Context, mgr manager.Manager) error { - if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1a2.TCPRoute{}, gatewayTCPRouteIndex, func(rawObj client.Object) []string { - tcpRoute := rawObj.(*gwapiv1a2.TCPRoute) - var gateways []string - for _, parent := range tcpRoute.Spec.ParentRefs { - if string(*parent.Kind) == resource.KindGateway { - // If an explicit Gateway namespace is not provided, use the TCPRoute namespace to - // lookup the provided Gateway Name. - gateways = append(gateways, - types.NamespacedName{ - Namespace: gatewayapi.NamespaceDerefOr(parent.Namespace, tcpRoute.Namespace), - Name: string(parent.Name), - }.String(), - ) - } - } - return gateways - }); err != nil { + if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1a2.TCPRoute{}, gatewayTCPRouteIndex, gatewayTCPRouteIndexFunc); err != nil { return err } @@ -429,6 +413,24 @@ func addTCPRouteIndexers(ctx context.Context, mgr manager.Manager) error { return nil } +func gatewayTCPRouteIndexFunc(rawObj client.Object) []string { + tcpRoute := rawObj.(*gwapiv1a2.TCPRoute) + var gateways []string + for _, parent := range tcpRoute.Spec.ParentRefs { + if string(*parent.Kind) == resource.KindGateway { + // If an explicit Gateway namespace is not provided, use the TCPRoute namespace to + // lookup the provided Gateway Name. + gateways = append(gateways, + types.NamespacedName{ + Namespace: gatewayapi.NamespaceDerefOr(parent.Namespace, tcpRoute.Namespace), + Name: string(parent.Name), + }.String(), + ) + } + } + return gateways +} + func backendTCPRouteIndexFunc(rawObj client.Object) []string { tcpRoute := rawObj.(*gwapiv1a2.TCPRoute) var backendRefs []string @@ -455,23 +457,7 @@ func backendTCPRouteIndexFunc(rawObj client.Object) []string { // - For Service objects that are referenced in UDPRoute objects via `.spec.rules.backendRefs`. This helps in // querying for UDPRoutes that are affected by a particular Service CRUD. func addUDPRouteIndexers(ctx context.Context, mgr manager.Manager) error { - if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1a2.UDPRoute{}, gatewayUDPRouteIndex, func(rawObj client.Object) []string { - udpRoute := rawObj.(*gwapiv1a2.UDPRoute) - var gateways []string - for _, parent := range udpRoute.Spec.ParentRefs { - if string(*parent.Kind) == resource.KindGateway { - // If an explicit Gateway namespace is not provided, use the UDPRoute namespace to - // lookup the provided Gateway Name. - gateways = append(gateways, - types.NamespacedName{ - Namespace: gatewayapi.NamespaceDerefOr(parent.Namespace, udpRoute.Namespace), - Name: string(parent.Name), - }.String(), - ) - } - } - return gateways - }); err != nil { + if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1a2.UDPRoute{}, gatewayUDPRouteIndex, gatewayUDPRouteIndexFunc); err != nil { return err } @@ -481,6 +467,24 @@ func addUDPRouteIndexers(ctx context.Context, mgr manager.Manager) error { return nil } +func gatewayUDPRouteIndexFunc(rawObj client.Object) []string { + udpRoute := rawObj.(*gwapiv1a2.UDPRoute) + var gateways []string + for _, parent := range udpRoute.Spec.ParentRefs { + if string(*parent.Kind) == resource.KindGateway { + // If an explicit Gateway namespace is not provided, use the UDPRoute namespace to + // lookup the provided Gateway Name. + gateways = append(gateways, + types.NamespacedName{ + Namespace: gatewayapi.NamespaceDerefOr(parent.Namespace, udpRoute.Namespace), + Name: string(parent.Name), + }.String(), + ) + } + } + return gateways +} + func backendUDPRouteIndexFunc(rawObj client.Object) []string { udproute := rawObj.(*gwapiv1a2.UDPRoute) var backendRefs []string @@ -509,10 +513,7 @@ func addGatewayIndexers(ctx context.Context, mgr manager.Manager) error { return err } - if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1.Gateway{}, classGatewayIndex, func(rawObj client.Object) []string { - gateway := rawObj.(*gwapiv1.Gateway) - return []string{string(gateway.Spec.GatewayClassName)} - }); err != nil { + if err := mgr.GetFieldIndexer().IndexField(ctx, &gwapiv1.Gateway{}, classGatewayIndex, gatewayIndexFunc); err != nil { return err } return nil @@ -541,6 +542,11 @@ func secretGatewayIndexFunc(rawObj client.Object) []string { return secretReferences } +func gatewayIndexFunc(rawObj client.Object) []string { + gateway := rawObj.(*gwapiv1.Gateway) + return []string{string(gateway.Spec.GatewayClassName)} +} + // addSecurityPolicyIndexers adds indexing on SecurityPolicy. // - For Secret objects that are referenced in SecurityPolicy objects via // `.spec.OIDC.clientSecret` and `.spec.basicAuth.users`. This helps in diff --git a/internal/provider/kubernetes/status_updater.go b/internal/provider/kubernetes/status_updater.go index 1bafe23668..c8b0472953 100644 --- a/internal/provider/kubernetes/status_updater.go +++ b/internal/provider/kubernetes/status_updater.go @@ -78,7 +78,7 @@ func (u *UpdateHandler) apply(update Update) { var ( startTime = time.Now() obj = update.Resource - objKind = kindOf(obj) + objKind = KindOf(obj) ) defer func() { @@ -297,7 +297,7 @@ func isStatusEqual(objA, objB interface{}) bool { return false } -// kindOf returns the known kind string for the given Kubernetes object, +// KindOf returns the known kind string for the given Kubernetes object, // returns Unknown for the unsupported object. // // Supported objects: @@ -316,7 +316,7 @@ func isStatusEqual(objA, objB interface{}) bool { // BackendTLSPolicy // EnvoyExtensionPolicy // Unstructured (for Extension Policies) -func kindOf(obj interface{}) string { +func KindOf(obj interface{}) string { var kind string switch o := obj.(type) { case *gwapiv1.GatewayClass: diff --git a/internal/provider/runner/runner.go b/internal/provider/runner/runner.go index a475e3204a..128184197a 100644 --- a/internal/provider/runner/runner.go +++ b/internal/provider/runner/runner.go @@ -53,7 +53,7 @@ func (r *Runner) Start(ctx context.Context) (err error) { } case egv1a1.ProviderTypeCustom: - p, err = r.createCustomResourceProvider() + p, err = r.createCustomResourceProvider(ctx) if err != nil { return fmt.Errorf("failed to create custom provider: %w", err) } @@ -87,10 +87,10 @@ func (r *Runner) createKubernetesProvider(ctx context.Context) (*kubernetes.Prov return p, err } -func (r *Runner) createCustomResourceProvider() (p provider.Provider, err error) { +func (r *Runner) createCustomResourceProvider(ctx context.Context) (p provider.Provider, err error) { switch r.EnvoyGateway.Provider.Custom.Resource.Type { case egv1a1.ResourceProviderTypeFile: - p, err = file.New(&r.Server, r.ProviderResources) + p, err = file.New(ctx, &r.Server, r.ProviderResources) if err != nil { return nil, fmt.Errorf("failed to create provider %s: %w", egv1a1.ProviderTypeCustom, err) } From 88a5273a8316ea96ae540a30841749974a6ea603 Mon Sep 17 00:00:00 2001 From: Kota Kimura <86363983+kkk777-7@users.noreply.github.com> Date: Wed, 7 May 2025 19:26:01 +0900 Subject: [PATCH 26/66] fix: SecurityPolicy reference grant (#5792) * fix: SecurityPolicy reference grant Signed-off-by: kkk777-7 * add: release note Signed-off-by: kkk777-7 * update: func name Signed-off-by: kkk777-7 * revert func name Signed-off-by: kkk777-7 * update: use processBackendRef to handle route backends Signed-off-by: kkk777-7 * fix: use not pointer type for extAuth backendRef Signed-off-by: kkk777-7 * Add: testcase for ExtAuth Signed-off-by: kkk777-7 * fix: add jwt backendref to backendSecurityPolicyIndexFunc Signed-off-by: kkk777-7 --------- Signed-off-by: kkk777-7 Signed-off-by: Arko Dasgupta --- internal/provider/kubernetes/controller.go | 175 +++--- .../provider/kubernetes/controller_test.go | 507 +++++++++++++++++- internal/provider/kubernetes/indexers.go | 40 +- internal/provider/kubernetes/routes.go | 258 +++------ release-notes/current.yaml | 1 + 5 files changed, 688 insertions(+), 293 deletions(-) diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go index 629cb41923..897b030dcf 100644 --- a/internal/provider/kubernetes/controller.go +++ b/internal/provider/kubernetes/controller.go @@ -609,57 +609,37 @@ func (r *gatewayAPIReconciler) processSecurityPolicyObjectRefs( // Add the referenced BackendRefs and ReferenceGrants in ExtAuth to Maps for later processing extAuth := policy.Spec.ExtAuth if extAuth != nil { - var backendRef *gwapiv1.BackendObjectReference + var backendRef gwapiv1.BackendObjectReference if extAuth.GRPC != nil { - backendRef = extAuth.GRPC.BackendRef + if extAuth.GRPC.BackendRef != nil { + backendRef = *extAuth.GRPC.BackendRef + } if len(extAuth.GRPC.BackendRefs) > 0 { if len(extAuth.GRPC.BackendRefs) != 0 { - backendRef = egv1a1.ToBackendObjectReference(extAuth.GRPC.BackendRefs[0]) + backendRef = extAuth.GRPC.BackendRefs[0].BackendObjectReference } } - } else { - backendRef = extAuth.HTTP.BackendRef + } else if extAuth.HTTP != nil { + if extAuth.HTTP.BackendRef != nil { + backendRef = *extAuth.HTTP.BackendRef + } if len(extAuth.HTTP.BackendRefs) > 0 { if len(extAuth.HTTP.BackendRefs) != 0 { - backendRef = egv1a1.ToBackendObjectReference(extAuth.HTTP.BackendRefs[0]) + backendRef = extAuth.HTTP.BackendRefs[0].BackendObjectReference } } } - - backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, policy.Namespace) - resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.Group, - Kind: backendRef.Kind, - Namespace: gatewayapi.NamespacePtr(backendNamespace), - Name: backendRef.Name, - }) - - if backendNamespace != policy.Namespace { - from := ObjectKindNamespacedName{ - kind: resource.KindSecurityPolicy, - namespace: policy.Namespace, - name: policy.Name, - } - to := ObjectKindNamespacedName{ - kind: gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService), - namespace: backendNamespace, - name: string(backendRef.Name), - } - refGrant, err := r.findReferenceGrant(ctx, from, to) - switch { - case err != nil: - r.log.Error(err, "failed to find ReferenceGrant") - case refGrant == nil: - r.log.Info("no matching ReferenceGrants found", "from", from.kind, - "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) - default: - if !resourceMap.allAssociatedReferenceGrants.Has(utils.NamespacedName(refGrant).String()) { - resourceMap.allAssociatedReferenceGrants.Insert(utils.NamespacedName(refGrant).String()) - resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) - r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, - "name", refGrant.Name) - } - } + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindSecurityPolicy, + policy.Namespace, + policy.Name, + backendRef); err != nil { + r.log.Error(err, + "failed to process ExtAuth BackendRef for SecurityPolicy", + "policy", policy, "backendRef", backendRef) } } @@ -682,12 +662,78 @@ func (r *gatewayAPIReconciler) processSecurityPolicyObjectRefs( }); err != nil { r.log.Error(err, "failed to process LocalJWKS ConfigMap", "policy", policy, "localJWKS", provider.LocalJWKS) } + } else if provider.RemoteJWKS != nil { + for _, br := range provider.RemoteJWKS.BackendRefs { + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindSecurityPolicy, + policy.Namespace, + policy.Name, + br.BackendObjectReference); err != nil { + r.log.Error(err, + "failed to process RemoteJWKS BackendRef for SecurityPolicy", + "policy", policy, "backendRef", br.BackendObjectReference) + } + } } } } } } +// processBackendRef adds the referenced BackendRef to the resourceMap for later processBackendRefs. +// If BackendRef exists in a different namespace and there is a ReferenceGrant, adds ReferenceGrant to the resourceTree. +func (r *gatewayAPIReconciler) processBackendRef( + ctx context.Context, + resourceMap *resourceMappings, + resourceTree *resource.Resources, + ownerKind string, + ownerNS string, + ownerName string, + backendRef gwapiv1.BackendObjectReference, +) error { + backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, ownerNS) + resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ + Group: backendRef.Group, + Kind: backendRef.Kind, + Namespace: gatewayapi.NamespacePtr(backendNamespace), + Name: backendRef.Name, + }) + + if backendNamespace != ownerNS { + from := ObjectKindNamespacedName{ + kind: ownerKind, + namespace: ownerNS, + name: ownerName, + } + to := ObjectKindNamespacedName{ + kind: gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService), + namespace: backendNamespace, + name: string(backendRef.Name), + } + refGrant, err := r.findReferenceGrant(ctx, from, to) + switch { + case err != nil: + return fmt.Errorf("failed to find ReferenceGrant: %w", err) + case refGrant == nil: + return fmt.Errorf( + "no matching ReferenceGrants found: from %s/%s to %s/%s", + from.kind, from.namespace, to.kind, to.namespace) + default: + // RefGrant found + if !resourceMap.allAssociatedReferenceGrants.Has(utils.NamespacedName(refGrant).String()) { + resourceMap.allAssociatedReferenceGrants.Insert(utils.NamespacedName(refGrant).String()) + resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) + r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, + "name", refGrant.Name) + } + } + } + return nil +} + // processOIDCHMACSecret adds the OIDC HMAC Secret to the resourceTree. // The OIDC HMAC Secret is created by the CertGen job and is used by SecurityPolicy // to configure OAuth2 filters. @@ -2184,42 +2230,17 @@ func (r *gatewayAPIReconciler) processEnvoyExtensionPolicyObjectRefs( // Add the referenced BackendRefs and ReferenceGrants in ExtAuth to Maps for later processing for _, ep := range policy.Spec.ExtProc { for _, br := range ep.BackendRefs { - backendRef := br.BackendObjectReference - - backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, policy.Namespace) - resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.Group, - Kind: backendRef.Kind, - Namespace: gatewayapi.NamespacePtr(backendNamespace), - Name: backendRef.Name, - }) - - if backendNamespace != policy.Namespace { - from := ObjectKindNamespacedName{ - kind: resource.KindEnvoyExtensionPolicy, - namespace: policy.Namespace, - name: policy.Name, - } - to := ObjectKindNamespacedName{ - kind: gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService), - namespace: backendNamespace, - name: string(backendRef.Name), - } - refGrant, err := r.findReferenceGrant(ctx, from, to) - switch { - case err != nil: - r.log.Error(err, "failed to find ReferenceGrant") - case refGrant == nil: - r.log.Info("no matching ReferenceGrants found", "from", from.kind, - "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) - default: - if !resourceMap.allAssociatedReferenceGrants.Has(utils.NamespacedName(refGrant).String()) { - resourceMap.allAssociatedReferenceGrants.Insert(utils.NamespacedName(refGrant).String()) - resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) - r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, - "name", refGrant.Name) - } - } + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindEnvoyExtensionPolicy, + policy.Namespace, + policy.Name, + br.BackendObjectReference); err != nil { + r.log.Error(err, + "failed to process ExtProc BackendRef for EnvoyExtensionPolicy", + "policy", policy, "backendRef", br.BackendObjectReference) } } } diff --git a/internal/provider/kubernetes/controller_test.go b/internal/provider/kubernetes/controller_test.go index 4ac85754f1..3914071992 100644 --- a/internal/provider/kubernetes/controller_test.go +++ b/internal/provider/kubernetes/controller_test.go @@ -420,27 +420,494 @@ func TestProcessEnvoyExtensionPolicyObjectRefs(t *testing.T) { t.Run(tc.name, func(t *testing.T) { // Add objects referenced by test cases. objs := []client.Object{tc.envoyExtensionPolicy, tc.backend, tc.referenceGrant} - - // Create the reconciler. - logger := logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo) + r := setupReferenceGrantReconciler(objs) ctx := context.Background() + resourceTree := resource.NewResources() + resourceMap := newResourceMapping() - r := &gatewayAPIReconciler{ - log: logger, - classController: "some-gateway-class", + err := r.processEnvoyExtensionPolicies(ctx, resourceTree, resourceMap) + require.NoError(t, err) + if tc.shouldBeAdded { + require.Contains(t, resourceTree.ReferenceGrants, tc.referenceGrant) + } else { + require.NotContains(t, resourceTree.ReferenceGrants, tc.referenceGrant) } + }) + } +} - r.client = fakeclient.NewClientBuilder(). - WithScheme(envoygateway.GetScheme()). - WithObjects(objs...). - WithIndex(&gwapiv1b1.ReferenceGrant{}, targetRefGrantRouteIndex, getReferenceGrantIndexerFunc). - Build() +func TestProcessSecurityPolicyObjectRefs(t *testing.T) { + testCases := []struct { + name string + securityPolicy *egv1a1.SecurityPolicy + backend *egv1a1.Backend + referenceGrant *gwapiv1b1.ReferenceGrant + shouldBeAdded bool + }{ + { + name: "valid security policy with remote jwks proper ref grant to backend", + securityPolicy: &egv1a1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "test-policy", + }, + Spec: egv1a1.SecurityPolicySpec{ + JWT: &egv1a1.JWT{ + Providers: []egv1a1.JWTProvider{ + { + RemoteJWKS: &egv1a1.RemoteJWKS{ + BackendCluster: egv1a1.BackendCluster{ + BackendRefs: []egv1a1.BackendRef{ + { + BackendObjectReference: gwapiv1.BackendObjectReference{ + Namespace: gatewayapi.NamespacePtr("ns-2"), + Name: "test-backend", + Kind: gatewayapi.KindPtr(resource.KindBackend), + Group: gatewayapi.GroupPtr(egv1a1.GroupName), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + backend: &egv1a1.Backend{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-backend", + }, + }, + referenceGrant: &gwapiv1b1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-grant", + }, + Spec: gwapiv1b1.ReferenceGrantSpec{ + From: []gwapiv1b1.ReferenceGrantFrom{ + { + Namespace: gwapiv1.Namespace("ns-1"), + Kind: gwapiv1.Kind(resource.KindSecurityPolicy), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + To: []gwapiv1b1.ReferenceGrantTo{ + { + Name: gatewayapi.ObjectNamePtr("test-backend"), + Kind: gwapiv1.Kind(resource.KindBackend), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + }, + }, + shouldBeAdded: true, + }, + { + name: "valid security policy with remote jwks wrong namespace ref grant to backend", + securityPolicy: &egv1a1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "test-policy", + }, + Spec: egv1a1.SecurityPolicySpec{ + JWT: &egv1a1.JWT{ + Providers: []egv1a1.JWTProvider{ + { + RemoteJWKS: &egv1a1.RemoteJWKS{ + BackendCluster: egv1a1.BackendCluster{ + BackendRefs: []egv1a1.BackendRef{ + { + BackendObjectReference: gwapiv1.BackendObjectReference{ + Namespace: gatewayapi.NamespacePtr("ns-2"), + Name: "test-backend", + Kind: gatewayapi.KindPtr(resource.KindBackend), + Group: gatewayapi.GroupPtr(egv1a1.GroupName), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + backend: &egv1a1.Backend{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-backend", + }, + }, + referenceGrant: &gwapiv1b1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-grant", + }, + Spec: gwapiv1b1.ReferenceGrantSpec{ + From: []gwapiv1b1.ReferenceGrantFrom{ + { + Namespace: gwapiv1.Namespace("ns-invalid"), + Kind: gwapiv1.Kind(resource.KindSecurityPolicy), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + To: []gwapiv1b1.ReferenceGrantTo{ + { + Name: gatewayapi.ObjectNamePtr("test-backend"), + Kind: gwapiv1.Kind(resource.KindBackend), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + }, + }, + shouldBeAdded: false, + }, + { + name: "valid security policy with extAuth grpc proper ref grant to backend", + securityPolicy: &egv1a1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "test-policy", + }, + Spec: egv1a1.SecurityPolicySpec{ + ExtAuth: &egv1a1.ExtAuth{ + GRPC: &egv1a1.GRPCExtAuthService{ + BackendCluster: egv1a1.BackendCluster{ + BackendRefs: []egv1a1.BackendRef{ + { + BackendObjectReference: gwapiv1.BackendObjectReference{ + Namespace: gatewayapi.NamespacePtr("ns-2"), + Name: "test-backend", + Kind: gatewayapi.KindPtr(resource.KindBackend), + Group: gatewayapi.GroupPtr(egv1a1.GroupName), + }, + }, + }, + }, + }, + }, + }, + }, + backend: &egv1a1.Backend{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-backend", + }, + }, + referenceGrant: &gwapiv1b1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-grant", + }, + Spec: gwapiv1b1.ReferenceGrantSpec{ + From: []gwapiv1b1.ReferenceGrantFrom{ + { + Namespace: gwapiv1.Namespace("ns-1"), + Kind: gwapiv1.Kind(resource.KindSecurityPolicy), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + To: []gwapiv1b1.ReferenceGrantTo{ + { + Name: gatewayapi.ObjectNamePtr("test-backend"), + Kind: gwapiv1.Kind(resource.KindBackend), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + }, + }, + shouldBeAdded: true, + }, + { + name: "valid security policy with extAuth grpc proper ref grant to backend (deprecated field)", + securityPolicy: &egv1a1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "test-policy", + }, + Spec: egv1a1.SecurityPolicySpec{ + ExtAuth: &egv1a1.ExtAuth{ + GRPC: &egv1a1.GRPCExtAuthService{ + BackendCluster: egv1a1.BackendCluster{ + BackendRef: &gwapiv1.BackendObjectReference{ + Namespace: gatewayapi.NamespacePtr("ns-2"), + Name: "test-backend", + Kind: gatewayapi.KindPtr(resource.KindBackend), + Group: gatewayapi.GroupPtr(egv1a1.GroupName), + }, + }, + }, + }, + }, + }, + backend: &egv1a1.Backend{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-backend", + }, + }, + referenceGrant: &gwapiv1b1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-grant", + }, + Spec: gwapiv1b1.ReferenceGrantSpec{ + From: []gwapiv1b1.ReferenceGrantFrom{ + { + Namespace: gwapiv1.Namespace("ns-1"), + Kind: gwapiv1.Kind(resource.KindSecurityPolicy), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + To: []gwapiv1b1.ReferenceGrantTo{ + { + Name: gatewayapi.ObjectNamePtr("test-backend"), + Kind: gwapiv1.Kind(resource.KindBackend), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + }, + }, + shouldBeAdded: true, + }, + { + name: "valid security policy with extAuth grpc wrong namespace ref grant to backend", + securityPolicy: &egv1a1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "test-policy", + }, + Spec: egv1a1.SecurityPolicySpec{ + ExtAuth: &egv1a1.ExtAuth{ + GRPC: &egv1a1.GRPCExtAuthService{ + BackendCluster: egv1a1.BackendCluster{ + BackendRefs: []egv1a1.BackendRef{ + { + BackendObjectReference: gwapiv1.BackendObjectReference{ + Namespace: gatewayapi.NamespacePtr("ns-2"), + Name: "test-backend", + Kind: gatewayapi.KindPtr(resource.KindBackend), + Group: gatewayapi.GroupPtr(egv1a1.GroupName), + }, + }, + }, + }, + }, + }, + }, + }, + backend: &egv1a1.Backend{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-backend", + }, + }, + referenceGrant: &gwapiv1b1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-grant", + }, + Spec: gwapiv1b1.ReferenceGrantSpec{ + From: []gwapiv1b1.ReferenceGrantFrom{ + { + Namespace: gwapiv1.Namespace("ns-invalid"), + Kind: gwapiv1.Kind(resource.KindSecurityPolicy), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + To: []gwapiv1b1.ReferenceGrantTo{ + { + Name: gatewayapi.ObjectNamePtr("test-backend"), + Kind: gwapiv1.Kind(resource.KindBackend), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + }, + }, + shouldBeAdded: false, + }, + { + name: "valid security policy with extAuth http proper ref grant to backend", + securityPolicy: &egv1a1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "test-policy", + }, + Spec: egv1a1.SecurityPolicySpec{ + ExtAuth: &egv1a1.ExtAuth{ + HTTP: &egv1a1.HTTPExtAuthService{ + BackendCluster: egv1a1.BackendCluster{ + BackendRefs: []egv1a1.BackendRef{ + { + BackendObjectReference: gwapiv1.BackendObjectReference{ + Namespace: gatewayapi.NamespacePtr("ns-2"), + Name: "test-backend", + Kind: gatewayapi.KindPtr(resource.KindBackend), + Group: gatewayapi.GroupPtr(egv1a1.GroupName), + }, + }, + }, + }, + }, + }, + }, + }, + backend: &egv1a1.Backend{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-backend", + }, + }, + referenceGrant: &gwapiv1b1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-grant", + }, + Spec: gwapiv1b1.ReferenceGrantSpec{ + From: []gwapiv1b1.ReferenceGrantFrom{ + { + Namespace: gwapiv1.Namespace("ns-1"), + Kind: gwapiv1.Kind(resource.KindSecurityPolicy), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + To: []gwapiv1b1.ReferenceGrantTo{ + { + Name: gatewayapi.ObjectNamePtr("test-backend"), + Kind: gwapiv1.Kind(resource.KindBackend), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + }, + }, + shouldBeAdded: true, + }, + { + name: "valid security policy with extAuth http proper ref grant to backend (deprecated field)", + securityPolicy: &egv1a1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "test-policy", + }, + Spec: egv1a1.SecurityPolicySpec{ + ExtAuth: &egv1a1.ExtAuth{ + HTTP: &egv1a1.HTTPExtAuthService{ + BackendCluster: egv1a1.BackendCluster{ + BackendRef: &gwapiv1.BackendObjectReference{ + Namespace: gatewayapi.NamespacePtr("ns-2"), + Name: "test-backend", + Kind: gatewayapi.KindPtr(resource.KindBackend), + Group: gatewayapi.GroupPtr(egv1a1.GroupName), + }, + }, + }, + }, + }, + }, + backend: &egv1a1.Backend{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-backend", + }, + }, + referenceGrant: &gwapiv1b1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-grant", + }, + Spec: gwapiv1b1.ReferenceGrantSpec{ + From: []gwapiv1b1.ReferenceGrantFrom{ + { + Namespace: gwapiv1.Namespace("ns-1"), + Kind: gwapiv1.Kind(resource.KindSecurityPolicy), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + To: []gwapiv1b1.ReferenceGrantTo{ + { + Name: gatewayapi.ObjectNamePtr("test-backend"), + Kind: gwapiv1.Kind(resource.KindBackend), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + }, + }, + shouldBeAdded: true, + }, + { + name: "valid security policy with extAuth http wrong namespace ref grant to backend", + securityPolicy: &egv1a1.SecurityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "test-policy", + }, + Spec: egv1a1.SecurityPolicySpec{ + ExtAuth: &egv1a1.ExtAuth{ + HTTP: &egv1a1.HTTPExtAuthService{ + BackendCluster: egv1a1.BackendCluster{ + BackendRefs: []egv1a1.BackendRef{ + { + BackendObjectReference: gwapiv1.BackendObjectReference{ + Namespace: gatewayapi.NamespacePtr("ns-2"), + Name: "test-backend", + Kind: gatewayapi.KindPtr(resource.KindBackend), + Group: gatewayapi.GroupPtr(egv1a1.GroupName), + }, + }, + }, + }, + }, + }, + }, + }, + backend: &egv1a1.Backend{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-backend", + }, + }, + referenceGrant: &gwapiv1b1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "test-grant", + }, + Spec: gwapiv1b1.ReferenceGrantSpec{ + From: []gwapiv1b1.ReferenceGrantFrom{ + { + Namespace: gwapiv1.Namespace("ns-invalid"), + Kind: gwapiv1.Kind(resource.KindSecurityPolicy), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + To: []gwapiv1b1.ReferenceGrantTo{ + { + Name: gatewayapi.ObjectNamePtr("test-backend"), + Kind: gwapiv1.Kind(resource.KindBackend), + Group: gwapiv1.Group(egv1a1.GroupName), + }, + }, + }, + }, + shouldBeAdded: false, + }, + } + + for i := range testCases { + tc := testCases[i] + // Run the test cases. + t.Run(tc.name, func(t *testing.T) { + // Add objects referenced by test cases. + objs := []client.Object{tc.securityPolicy, tc.backend, tc.referenceGrant} + r := setupReferenceGrantReconciler(objs) + ctx := context.Background() resourceTree := resource.NewResources() resourceMap := newResourceMapping() - err := r.processEnvoyExtensionPolicies(ctx, resourceTree, resourceMap) + err := r.processSecurityPolicies(ctx, resourceTree, resourceMap) require.NoError(t, err) if tc.shouldBeAdded { require.Contains(t, resourceTree.ReferenceGrants, tc.referenceGrant) @@ -450,3 +917,19 @@ func TestProcessEnvoyExtensionPolicyObjectRefs(t *testing.T) { }) } } + +func setupReferenceGrantReconciler(objs []client.Object) *gatewayAPIReconciler { + logger := logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo) + + r := &gatewayAPIReconciler{ + log: logger, + classController: "some-gateway-class", + } + + r.client = fakeclient.NewClientBuilder(). + WithScheme(envoygateway.GetScheme()). + WithObjects(objs...). + WithIndex(&gwapiv1b1.ReferenceGrant{}, targetRefGrantRouteIndex, getReferenceGrantIndexerFunc). + Build() + return r +} diff --git a/internal/provider/kubernetes/indexers.go b/internal/provider/kubernetes/indexers.go index 5114ce5882..17fcb85968 100644 --- a/internal/provider/kubernetes/indexers.go +++ b/internal/provider/kubernetes/indexers.go @@ -610,35 +610,49 @@ func secretSecurityPolicyIndexFunc(rawObj client.Object) []string { func backendSecurityPolicyIndexFunc(rawObj client.Object) []string { securityPolicy := rawObj.(*egv1a1.SecurityPolicy) - var backendRef *gwapiv1.BackendObjectReference + var ( + backendRefs []gwapiv1.BackendObjectReference + values []string + ) if securityPolicy.Spec.ExtAuth != nil { if securityPolicy.Spec.ExtAuth.HTTP != nil { http := securityPolicy.Spec.ExtAuth.HTTP - backendRef = http.BackendRef + if http.BackendRef != nil { + backendRefs = append(backendRefs, *http.BackendRef) + } if len(http.BackendRefs) > 0 { - backendRef = egv1a1.ToBackendObjectReference(http.BackendRefs[0]) + backendRefs = append(backendRefs, http.BackendRefs[0].BackendObjectReference) } } else if securityPolicy.Spec.ExtAuth.GRPC != nil { grpc := securityPolicy.Spec.ExtAuth.GRPC - backendRef = grpc.BackendRef + if grpc.BackendRef != nil { + backendRefs = append(backendRefs, *grpc.BackendRef) + } if len(grpc.BackendRefs) > 0 { - backendRef = egv1a1.ToBackendObjectReference(grpc.BackendRefs[0]) + backendRefs = append(backendRefs, grpc.BackendRefs[0].BackendObjectReference) + } + } + } + if securityPolicy.Spec.JWT != nil { + for _, provider := range securityPolicy.Spec.JWT.Providers { + if provider.RemoteJWKS != nil { + for _, backendRef := range provider.RemoteJWKS.BackendRefs { + backendRefs = append(backendRefs, backendRef.BackendObjectReference) + } } } } - if backendRef != nil { - return []string{ + for _, reference := range backendRefs { + values = append(values, types.NamespacedName{ - Namespace: gatewayapi.NamespaceDerefOr(backendRef.Namespace, securityPolicy.Namespace), - Name: string(backendRef.Name), + Namespace: gatewayapi.NamespaceDerefOr(reference.Namespace, securityPolicy.Namespace), + Name: string(reference.Name), }.String(), - } + ) } - - // This should not happen because the CEL validation should catch it. - return []string{} + return values } func configMapSecurityPolicyIndexFunc(rawObj client.Object) []string { diff --git a/internal/provider/kubernetes/routes.go b/internal/provider/kubernetes/routes.go index f2c676066f..20f8ad292d 100644 --- a/internal/provider/kubernetes/routes.go +++ b/internal/provider/kubernetes/routes.go @@ -61,34 +61,17 @@ func (r *gatewayAPIReconciler) processTLSRoutes(ctx context.Context, gatewayName r.log.Error(err, "invalid backendRef") continue } - - backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, tlsRoute.Namespace) - resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.Group, - Kind: backendRef.Kind, - Namespace: gatewayapi.NamespacePtr(backendNamespace), - Name: backendRef.Name, - }) - - if backendNamespace != tlsRoute.Namespace { - from := ObjectKindNamespacedName{kind: resource.KindTLSRoute, namespace: tlsRoute.Namespace, name: tlsRoute.Name} - to := ObjectKindNamespacedName{kind: gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService), namespace: backendNamespace, name: string(backendRef.Name)} - refGrant, err := r.findReferenceGrant(ctx, from, to) - switch { - case err != nil: - r.log.Error(err, "failed to find ReferenceGrant") - case refGrant == nil: - r.log.Info("no matching ReferenceGrants found", "from", from.kind, - "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) - default: - refGrantNamespacedName := utils.NamespacedName(refGrant).String() - if !resourceMap.allAssociatedReferenceGrants.Has(refGrantNamespacedName) { - resourceMap.allAssociatedReferenceGrants.Insert(refGrantNamespacedName) - resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) - r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, - "name", refGrant.Name) - } - } + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindTLSRoute, + tlsRoute.Namespace, + tlsRoute.Name, + backendRef.BackendObjectReference); err != nil { + r.log.Error(err, + "failed to process BackendRef for TLSRoute", + "tlsRoute", tlsRoute, "backendRef", backendRef.BackendObjectReference) } } } @@ -143,42 +126,17 @@ func (r *gatewayAPIReconciler) processGRPCRoutes(ctx context.Context, gatewayNam r.log.Error(err, "invalid backendRef") continue } - - backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, grpcRoute.Namespace) - resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.Group, - Kind: backendRef.Kind, - Namespace: gatewayapi.NamespacePtr(backendNamespace), - Name: backendRef.Name, - }) - - if backendNamespace != grpcRoute.Namespace { - from := ObjectKindNamespacedName{ - kind: resource.KindGRPCRoute, - namespace: grpcRoute.Namespace, - name: grpcRoute.Name, - } - to := ObjectKindNamespacedName{ - kind: gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService), - namespace: backendNamespace, - name: string(backendRef.Name), - } - refGrant, err := r.findReferenceGrant(ctx, from, to) - switch { - case err != nil: - r.log.Error(err, "failed to find ReferenceGrant") - case refGrant == nil: - r.log.Info("no matching ReferenceGrants found", "from", from.kind, - "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) - default: - refGrantNamespacedName := utils.NamespacedName(refGrant).String() - if !resourceMap.allAssociatedReferenceGrants.Has(refGrantNamespacedName) { - resourceMap.allAssociatedReferenceGrants.Insert(refGrantNamespacedName) - resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) - r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, - "name", refGrant.Name) - } - } + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindGRPCRoute, + grpcRoute.Namespace, + grpcRoute.Name, + backendRef.BackendObjectReference); err != nil { + r.log.Error(err, + "failed to process BackendRef for GRPCRoute", + "grpcRoute", grpcRoute, "backendRef", backendRef.BackendObjectReference) } } @@ -281,43 +239,19 @@ func (r *gatewayAPIReconciler) processHTTPRoutes(ctx context.Context, gatewayNam r.log.Error(err, "invalid backendRef") continue } - - backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, httpRoute.Namespace) - resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.Group, - Kind: backendRef.Kind, - Namespace: gatewayapi.NamespacePtr(backendNamespace), - Name: backendRef.Name, - }) - - if backendNamespace != httpRoute.Namespace { - from := ObjectKindNamespacedName{ - kind: resource.KindHTTPRoute, - namespace: httpRoute.Namespace, - name: httpRoute.Name, - } - to := ObjectKindNamespacedName{ - kind: gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService), - namespace: backendNamespace, - name: string(backendRef.Name), - } - refGrant, err := r.findReferenceGrant(ctx, from, to) - switch { - case err != nil: - r.log.Error(err, "failed to find ReferenceGrant") - case refGrant == nil: - r.log.Info("no matching ReferenceGrants found", "from", from.kind, - "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) - default: - refGrantNamespacedName := utils.NamespacedName(refGrant).String() - if !resourceMap.allAssociatedReferenceGrants.Has(refGrantNamespacedName) { - resourceMap.allAssociatedReferenceGrants.Insert(refGrantNamespacedName) - resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) - r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, - "name", refGrant.Name) - } - } + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindHTTPRoute, + httpRoute.Namespace, + httpRoute.Name, + backendRef.BackendObjectReference); err != nil { + r.log.Error(err, + "failed to process BackendRef for HTTPRoute", + "httpRoute", httpRoute, "backendRef", backendRef.BackendObjectReference) } + for i := range backendRef.Filters { // Some of the validation logic in processHTTPRouteFilter is not needed for backendRef filters. // However, we reuse the same function to avoid code duplication. @@ -382,42 +316,17 @@ func (r *gatewayAPIReconciler) processHTTPRouteFilter( if err := validateBackendRef(&mirrorBackendRef); err != nil { return fmt.Errorf("invalid backendRef for requestMirror filter: %w", err) } - - backendNamespace := gatewayapi.NamespaceDerefOr(mirrorBackendRef.Namespace, httpRoute.Namespace) - resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: mirrorBackendRef.Group, - Kind: mirrorBackendRef.Kind, - Namespace: gatewayapi.NamespacePtr(backendNamespace), - Name: mirrorBackendRef.Name, - }) - - if backendNamespace != httpRoute.Namespace { - from := ObjectKindNamespacedName{ - kind: resource.KindHTTPRoute, - namespace: httpRoute.Namespace, - name: httpRoute.Name, - } - to := ObjectKindNamespacedName{ - kind: gatewayapi.KindDerefOr(mirrorBackendRef.Kind, resource.KindService), - namespace: backendNamespace, - name: string(mirrorBackendRef.Name), - } - refGrant, err := r.findReferenceGrant(ctx, from, to) - switch { - case err != nil: - r.log.Error(err, "failed to find ReferenceGrant") - case refGrant == nil: - r.log.Info("no matching ReferenceGrants found", "from", from.kind, - "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) - default: - refGrantNamespacedName := utils.NamespacedName(refGrant).String() - if !resourceMap.allAssociatedReferenceGrants.Has(refGrantNamespacedName) { - resourceMap.allAssociatedReferenceGrants.Insert(refGrantNamespacedName) - resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) - r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, - "name", refGrant.Name) - } - } + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindHTTPRoute, + httpRoute.Namespace, + httpRoute.Name, + mirrorBackendRef.BackendObjectReference); err != nil { + r.log.Error(err, + "failed to process BackendRef for HTTPRouteFilter", + "httpRoute", httpRoute, "backendRef", mirrorBackendRef.BackendObjectReference) } case gwapiv1.HTTPRouteFilterExtensionRef: // NOTE: filters must be in the same namespace as the HTTPRoute @@ -500,34 +409,17 @@ func (r *gatewayAPIReconciler) processTCPRoutes(ctx context.Context, gatewayName r.log.Error(err, "invalid backendRef") continue } - - backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, tcpRoute.Namespace) - resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.Group, - Kind: backendRef.Kind, - Namespace: gatewayapi.NamespacePtr(backendNamespace), - Name: backendRef.Name, - }) - - if backendNamespace != tcpRoute.Namespace { - from := ObjectKindNamespacedName{kind: resource.KindTCPRoute, namespace: tcpRoute.Namespace, name: tcpRoute.Name} - to := ObjectKindNamespacedName{kind: gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService), namespace: backendNamespace, name: string(backendRef.Name)} - refGrant, err := r.findReferenceGrant(ctx, from, to) - switch { - case err != nil: - r.log.Error(err, "failed to find ReferenceGrant") - case refGrant == nil: - r.log.Info("no matching ReferenceGrants found", "from", from.kind, - "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) - default: - refGrantNamespacedName := utils.NamespacedName(refGrant).String() - if !resourceMap.allAssociatedReferenceGrants.Has(refGrantNamespacedName) { - resourceMap.allAssociatedReferenceGrants.Insert(refGrantNamespacedName) - resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) - r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, - "name", refGrant.Name) - } - } + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindTCPRoute, + tcpRoute.Namespace, + tcpRoute.Name, + backendRef.BackendObjectReference); err != nil { + r.log.Error(err, + "failed to process BackendRef for TCPRoute", + "tcpRoute", tcpRoute, "backendRef", backendRef.BackendObjectReference) } } } @@ -581,33 +473,17 @@ func (r *gatewayAPIReconciler) processUDPRoutes(ctx context.Context, gatewayName r.log.Error(err, "invalid backendRef") continue } - - backendNamespace := gatewayapi.NamespaceDerefOr(backendRef.Namespace, udpRoute.Namespace) - resourceMap.allAssociatedBackendRefs.Insert(gwapiv1.BackendObjectReference{ - Group: backendRef.Group, - Kind: backendRef.Kind, - Namespace: gatewayapi.NamespacePtr(backendNamespace), - Name: backendRef.Name, - }) - - if backendNamespace != udpRoute.Namespace { - from := ObjectKindNamespacedName{kind: resource.KindUDPRoute, namespace: udpRoute.Namespace, name: udpRoute.Name} - to := ObjectKindNamespacedName{kind: gatewayapi.KindDerefOr(backendRef.Kind, resource.KindService), namespace: backendNamespace, name: string(backendRef.Name)} - refGrant, err := r.findReferenceGrant(ctx, from, to) - switch { - case err != nil: - r.log.Error(err, "failed to find ReferenceGrant") - case refGrant == nil: - r.log.Info("no matching ReferenceGrants found", "from", from.kind, - "from namespace", from.namespace, "target", to.kind, "target namespace", to.namespace) - default: - if !resourceMap.allAssociatedReferenceGrants.Has(utils.NamespacedName(refGrant).String()) { - resourceMap.allAssociatedReferenceGrants.Insert(utils.NamespacedName(refGrant).String()) - resourceTree.ReferenceGrants = append(resourceTree.ReferenceGrants, refGrant) - r.log.Info("added ReferenceGrant to resource map", "namespace", refGrant.Namespace, - "name", refGrant.Name) - } - } + if err := r.processBackendRef( + ctx, + resourceMap, + resourceTree, + resource.KindUDPRoute, + udpRoute.Namespace, + udpRoute.Name, + backendRef.BackendObjectReference); err != nil { + r.log.Error(err, + "failed to process BackendRef for UDPRoute", + "udpRoute", udpRoute, "backendRef", backendRef.BackendObjectReference) } } } diff --git a/release-notes/current.yaml b/release-notes/current.yaml index 3271a4f907..e4a3d75cdf 100644 --- a/release-notes/current.yaml +++ b/release-notes/current.yaml @@ -10,6 +10,7 @@ security updates: | new features: | bug fixes: | + Fix reference grant from SecurityPolicy to referenced remoteJWKS backend not respected. # Enhancements that improve performance. performance improvements: | From 74ae8ff086c82eee705dca0bd7eecf025b5b1fcc Mon Sep 17 00:00:00 2001 From: Gavin Lam Date: Wed, 7 May 2025 17:37:40 -0400 Subject: [PATCH 27/66] fix: add validation for header values (#5933) Signed-off-by: Gavin Lam Signed-off-by: Arko Dasgupta --- internal/gatewayapi/clienttrafficpolicy.go | 14 +- internal/gatewayapi/filters.go | 46 +++++- .../clienttrafficpolicy-headers-error.in.yaml | 9 +- ...clienttrafficpolicy-headers-error.out.yaml | 16 ++- ...-header-filter-empty-header-values.in.yaml | 2 +- ...header-filter-empty-header-values.out.yaml | 40 +----- ...eader-filter-invalid-header-values.in.yaml | 47 ++++++ ...ader-filter-invalid-header-values.out.yaml | 134 ++++++++++++++++++ ...-header-filter-empty-header-values.in.yaml | 3 +- ...header-filter-empty-header-values.out.yaml | 40 +----- ...eader-filter-invalid-header-values.in.yaml | 46 ++++++ ...ader-filter-invalid-header-values.out.yaml | 134 ++++++++++++++++++ release-notes/current.yaml | 1 + 13 files changed, 451 insertions(+), 81 deletions(-) create mode 100644 internal/gatewayapi/testdata/httproute-with-header-filter-invalid-header-values.in.yaml create mode 100644 internal/gatewayapi/testdata/httproute-with-header-filter-invalid-header-values.out.yaml create mode 100644 internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-header-values.in.yaml create mode 100644 internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-header-values.out.yaml diff --git a/internal/gatewayapi/clienttrafficpolicy.go b/internal/gatewayapi/clienttrafficpolicy.go index f2585a7dd1..843109838f 100644 --- a/internal/gatewayapi/clienttrafficpolicy.go +++ b/internal/gatewayapi/clienttrafficpolicy.go @@ -952,7 +952,12 @@ func translateEarlyRequestHeaders(headerModifier *gwapiv1.HTTPHeaderFilter) ([]i } // Per Gateway API specification on HTTPHeaderName, : and / are invalid characters in header names if strings.ContainsAny(string(addHeader.Name), "/:") { - errs = errors.Join(errs, fmt.Errorf("EarlyRequestHeaders Filter cannot set headers with a '/' or ':' character in them. Header: %q", string(addHeader.Name))) + errs = errors.Join(errs, fmt.Errorf("EarlyRequestHeaders cannot add a header with a '/' or ':' character in them. Header: '%q'", string(addHeader.Name))) + continue + } + // Gateway API specification allows only valid value as defined by RFC 7230 + if !HeaderValueRegexp.MatchString(addHeader.Value) { + errs = errors.Join(errs, fmt.Errorf("EarlyRequestHeaders cannot add a header with an invalid value. Header: '%q'", string(addHeader.Name))) continue } // Check if the header is a duplicate @@ -992,7 +997,12 @@ func translateEarlyRequestHeaders(headerModifier *gwapiv1.HTTPHeaderFilter) ([]i } // Per Gateway API specification on HTTPHeaderName, : and / are invalid characters in header names if strings.ContainsAny(string(setHeader.Name), "/:") { - errs = errors.Join(errs, fmt.Errorf("EarlyRequestHeaders cannot set headers with a '/' or ':' character in them. Header: '%s'", string(setHeader.Name))) + errs = errors.Join(errs, fmt.Errorf("EarlyRequestHeaders cannot set a header with a '/' or ':' character in them. Header: '%q'", string(setHeader.Name))) + continue + } + // Gateway API specification allows only valid value as defined by RFC 7230 + if !HeaderValueRegexp.MatchString(setHeader.Value) { + errs = errors.Join(errs, fmt.Errorf("EarlyRequestHeaders cannot set a header with an invalid value. Header: '%q'", string(setHeader.Name))) continue } diff --git a/internal/gatewayapi/filters.go b/internal/gatewayapi/filters.go index 0ca0dcd6ff..27c4a93ead 100644 --- a/internal/gatewayapi/filters.go +++ b/internal/gatewayapi/filters.go @@ -66,6 +66,9 @@ type HTTPFilterIR struct { ExtensionRefs []*ir.UnstructuredRef } +// Header value pattern according to RFC 7230 +var HeaderValueRegexp = regexp.MustCompile(`^[!-~]+([\t ]?[!-~]+)*$`) + // ProcessHTTPFilters translates gateway api http filters to IRs. func (t *Translator) ProcessHTTPFilters(parentRef *RouteParentContext, route RouteContext, @@ -376,6 +379,7 @@ func (t *Translator) processRequestHeaderModifierFilter( emptyFilterConfig = false } for _, addHeader := range headersToAdd { + emptyFilterConfig = false if addHeader.Name == "" { updateRouteStatusForFilter( @@ -384,16 +388,27 @@ func (t *Translator) processRequestHeaderModifierFilter( // try to process the rest of the headers and produce a valid config. continue } + if !isModifiableHeader(string(addHeader.Name)) { updateRouteStatusForFilter( filterContext, fmt.Sprintf( - "Header: %q. The RequestHeaderModifier filter cannot set the Host header or headers with a '/' "+ + "Header: %q. The RequestHeaderModifier filter cannot add the Host header or headers with a '/' "+ "or ':' character in them. To modify the Host header use the URLRewrite or the HTTPRouteFilter filter.", string(addHeader.Name)), ) continue } + + if !HeaderValueRegexp.MatchString(addHeader.Value) { + updateRouteStatusForFilter( + filterContext, + fmt.Sprintf( + "Header: %q. RequestHeaderModifier Filter cannot add a header with an invalid value.", + string(addHeader.Name))) + continue + } + // Check if the header is a duplicate headerKey := string(addHeader.Name) canAddHeader := true @@ -443,6 +458,15 @@ func (t *Translator) processRequestHeaderModifierFilter( continue } + if !HeaderValueRegexp.MatchString(setHeader.Value) { + updateRouteStatusForFilter( + filterContext, + fmt.Sprintf( + "Header: %q. RequestHeaderModifier Filter cannot set a header with an invalid value.", + string(setHeader.Name))) + continue + } + // Check if the header to be set has already been configured headerKey := string(setHeader.Name) canAddHeader := true @@ -556,6 +580,7 @@ func (t *Translator) processResponseHeaderModifierFilter( // try to process the rest of the headers and produce a valid config. continue } + if !isModifiableHeader(string(addHeader.Name)) { updateRouteStatusForFilter( filterContext, @@ -565,6 +590,16 @@ func (t *Translator) processResponseHeaderModifierFilter( string(addHeader.Name))) continue } + + if !HeaderValueRegexp.MatchString(addHeader.Value) { + updateRouteStatusForFilter( + filterContext, + fmt.Sprintf( + "Header: %q. ResponseHeaderModifier Filter cannot add a header with an invalid value.", + string(addHeader.Name))) + continue + } + // Check if the header is a duplicate headerKey := string(addHeader.Name) canAddHeader := true @@ -613,6 +648,15 @@ func (t *Translator) processResponseHeaderModifierFilter( continue } + if !HeaderValueRegexp.MatchString(setHeader.Value) { + updateRouteStatusForFilter( + filterContext, + fmt.Sprintf( + "Header: %q. ResponseHeaderModifier Filter cannot set a header with an invalid value.", + string(setHeader.Name))) + continue + } + // Check if the header to be set has already been configured headerKey := string(setHeader.Name) canAddHeader := true diff --git a/internal/gatewayapi/testdata/clienttrafficpolicy-headers-error.in.yaml b/internal/gatewayapi/testdata/clienttrafficpolicy-headers-error.in.yaml index 3b2331bba7..5fb1efb474 100644 --- a/internal/gatewayapi/testdata/clienttrafficpolicy-headers-error.in.yaml +++ b/internal/gatewayapi/testdata/clienttrafficpolicy-headers-error.in.yaml @@ -13,13 +13,18 @@ clientTrafficPolicies: add: - name: "" value: "empty" - - name: "invalid" + - name: "Example/Header/1" value: ":/" + - name: "example-header-2" + value: | + multi-line-header-value set: - name: "" value: "empty" - - name: "invalid" + - name: "Example:Header:3" value: ":/" + - name: "example-header-4" + value: " invalid" remove: - "" targetRef: diff --git a/internal/gatewayapi/testdata/clienttrafficpolicy-headers-error.out.yaml b/internal/gatewayapi/testdata/clienttrafficpolicy-headers-error.out.yaml index abab24926f..4a12476ea7 100644 --- a/internal/gatewayapi/testdata/clienttrafficpolicy-headers-error.out.yaml +++ b/internal/gatewayapi/testdata/clienttrafficpolicy-headers-error.out.yaml @@ -11,15 +11,20 @@ clientTrafficPolicies: add: - name: "" value: empty - - name: invalid + - name: Example/Header/1 value: :/ + - name: example-header-2 + value: | + multi-line-header-value remove: - "" set: - name: "" value: empty - - name: invalid + - name: Example:Header:3 value: :/ + - name: example-header-4 + value: ' invalid' enableEnvoyHeaders: true preserveXRequestID: true withUnderscoresAction: Allow @@ -38,8 +43,13 @@ clientTrafficPolicies: - lastTransitionTime: null message: |- Headers: EarlyRequestHeaders cannot add a header with an empty name + EarlyRequestHeaders cannot add a header with a '/' or ':' character in them. Header: '"Example/Header/1"' + EarlyRequestHeaders cannot add a header with an invalid value. Header: '"example-header-2"' EarlyRequestHeaders cannot set a header with an empty name - EarlyRequestHeaders cannot remove a header with an empty name. + EarlyRequestHeaders cannot set a header with a '/' or ':' character in them. Header: '"Example:Header:3"' + EarlyRequestHeaders cannot set a header with an invalid value. Header: '"example-header-4"' + EarlyRequestHeaders cannot remove a header with an empty name + EarlyRequestHeaders did not provide valid configuration to add/set/remove any headers. reason: Invalid status: "False" type: Accepted diff --git a/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.in.yaml b/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.in.yaml index c965d32968..0037ee5543 100644 --- a/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.in.yaml +++ b/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.in.yaml @@ -39,7 +39,7 @@ httpRoutes: requestHeaderModifier: set: - name: "example-header-1" - value: "" + value: "dummy" add: - name: "example-header-2" value: "" diff --git a/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.out.yaml b/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.out.yaml index 98c6a40af9..ba290a7ff0 100644 --- a/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.out.yaml +++ b/internal/gatewayapi/testdata/httproute-with-header-filter-empty-header-values.out.yaml @@ -65,7 +65,7 @@ httpRoutes: value: "" set: - name: example-header-1 - value: "" + value: dummy type: RequestHeaderModifier matches: - path: @@ -74,9 +74,10 @@ httpRoutes: parents: - conditions: - lastTransitionTime: null - message: Route is accepted - reason: Accepted - status: "True" + message: 'Header: "example-header-2". RequestHeaderModifier Filter cannot + add a header with an invalid value.' + reason: UnsupportedValue + status: "False" type: Accepted - lastTransitionTime: null message: Resolved all the Object references for the Route @@ -125,37 +126,6 @@ xdsIR: escapedSlashesAction: UnescapeAndRedirect mergeSlashes: true port: 10080 - routes: - - addRequestHeaders: - - append: true - name: example-header-2 - value: - - "" - - append: false - name: example-header-1 - value: - - "" - destination: - name: httproute/default/httproute-1/rule/0 - settings: - - addressType: IP - endpoints: - - host: 7.7.7.7 - port: 8080 - name: httproute/default/httproute-1/rule/0/backend/0 - protocol: HTTP - weight: 1 - hostname: gateway.envoyproxy.io - isHTTP2: false - metadata: - kind: HTTPRoute - name: httproute-1 - namespace: default - name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io - pathMatch: - distinct: false - name: "" - prefix: / readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-header-values.in.yaml b/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-header-values.in.yaml new file mode 100644 index 0000000000..75d1c1669d --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-header-values.in.yaml @@ -0,0 +1,47 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + hostname: "*.envoyproxy.io" + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 + filters: + - type: RequestHeaderModifier + requestHeaderModifier: + set: + - name: "example-header-1" + value: | + multi-line-header-value + add: + - name: "example-header-2" + value: "dummy" + diff --git a/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-header-values.out.yaml b/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-header-values.out.yaml new file mode 100644 index 0000000000..fe02e7a8c4 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-header-filter-invalid-header-values.out.yaml @@ -0,0 +1,134 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + hostname: '*.envoyproxy.io' + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + filters: + - requestHeaderModifier: + add: + - name: example-header-2 + value: dummy + set: + - name: example-header-1 + value: | + multi-line-header-value + type: RequestHeaderModifier + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: 'Header: "example-header-1". RequestHeaderModifier Filter cannot + set a header with an invalid value.' + reason: UnsupportedValue + status: "False" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*.envoyproxy.io' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.in.yaml b/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.in.yaml index 1c0016800d..b84f703b65 100644 --- a/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.in.yaml +++ b/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.in.yaml @@ -39,8 +39,7 @@ httpRoutes: responseHeaderModifier: set: - name: "example-header-1" - value: "" + value: "dummy" add: - name: "example-header-2" value: "" - diff --git a/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.out.yaml b/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.out.yaml index f89eab6f3f..94dae9d730 100644 --- a/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.out.yaml +++ b/internal/gatewayapi/testdata/httproute-with-response-header-filter-empty-header-values.out.yaml @@ -65,7 +65,7 @@ httpRoutes: value: "" set: - name: example-header-1 - value: "" + value: dummy type: ResponseHeaderModifier matches: - path: @@ -74,9 +74,10 @@ httpRoutes: parents: - conditions: - lastTransitionTime: null - message: Route is accepted - reason: Accepted - status: "True" + message: 'Header: "example-header-2". ResponseHeaderModifier Filter cannot + add a header with an invalid value.' + reason: UnsupportedValue + status: "False" type: Accepted - lastTransitionTime: null message: Resolved all the Object references for the Route @@ -125,37 +126,6 @@ xdsIR: escapedSlashesAction: UnescapeAndRedirect mergeSlashes: true port: 10080 - routes: - - addResponseHeaders: - - append: true - name: example-header-2 - value: - - "" - - append: false - name: example-header-1 - value: - - "" - destination: - name: httproute/default/httproute-1/rule/0 - settings: - - addressType: IP - endpoints: - - host: 7.7.7.7 - port: 8080 - name: httproute/default/httproute-1/rule/0/backend/0 - protocol: HTTP - weight: 1 - hostname: gateway.envoyproxy.io - isHTTP2: false - metadata: - kind: HTTPRoute - name: httproute-1 - namespace: default - name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io - pathMatch: - distinct: false - name: "" - prefix: / readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-header-values.in.yaml b/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-header-values.in.yaml new file mode 100644 index 0000000000..802391150f --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-header-values.in.yaml @@ -0,0 +1,46 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + hostname: "*.envoyproxy.io" + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 + filters: + - type: ResponseHeaderModifier + responseHeaderModifier: + set: + - name: "example-header-1" + value: | + multi-line-header-value + add: + - name: "example-header-2" + value: "dummy" diff --git a/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-header-values.out.yaml b/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-header-values.out.yaml new file mode 100644 index 0000000000..2ceb5619c2 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-response-header-filter-invalid-header-values.out.yaml @@ -0,0 +1,134 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + hostname: '*.envoyproxy.io' + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + filters: + - responseHeaderModifier: + add: + - name: example-header-2 + value: dummy + set: + - name: example-header-1 + value: | + multi-line-header-value + type: ResponseHeaderModifier + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: 'Header: "example-header-1". ResponseHeaderModifier Filter cannot + set a header with an invalid value.' + reason: UnsupportedValue + status: "False" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*.envoyproxy.io' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/release-notes/current.yaml b/release-notes/current.yaml index e4a3d75cdf..ea823a6d3f 100644 --- a/release-notes/current.yaml +++ b/release-notes/current.yaml @@ -11,6 +11,7 @@ new features: | bug fixes: | Fix reference grant from SecurityPolicy to referenced remoteJWKS backend not respected. + Added validation for header values. # Enhancements that improve performance. performance improvements: | From dbbecbab6bc10d15f1c97e6700cada7e70a827f9 Mon Sep 17 00:00:00 2001 From: Mathias Westby Skoglund <71329699+mathias-ws@users.noreply.github.com> Date: Thu, 8 May 2025 00:35:19 +0200 Subject: [PATCH 28/66] fix: Fixed typo in error message. (#5945) Signed-off-by: Mathias Westby Skoglund Co-authored-by: Mathias Westby Skoglund Signed-off-by: Arko Dasgupta --- internal/provider/kubernetes/kubernetes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/provider/kubernetes/kubernetes.go b/internal/provider/kubernetes/kubernetes.go index 3346714367..ac19c145aa 100644 --- a/internal/provider/kubernetes/kubernetes.go +++ b/internal/provider/kubernetes/kubernetes.go @@ -138,7 +138,7 @@ func New(ctx context.Context, restCfg *rest.Config, svrCfg *ec.Server, resources // Create and register the controllers with the manager. if err := newGatewayAPIController(ctx, mgr, svrCfg, updateHandler.Writer(), resources); err != nil { - return nil, fmt.Errorf("failted to create gatewayapi controller: %w", err) + return nil, fmt.Errorf("failed to create gatewayapi controller: %w", err) } // Add health check health probes. From 208921fee85ca93c2142ba0b8c4ac84dc81467f0 Mon Sep 17 00:00:00 2001 From: "Huabing (Robin) Zhao" Date: Thu, 8 May 2025 18:44:43 +0800 Subject: [PATCH 29/66] e2e: disable DynamicResolverBackendTest on IPv6 (#5964) disable DynamicResolverBackendTest in IPV6 Signed-off-by: Huabing (Robin) Zhao Signed-off-by: Arko Dasgupta --- test/e2e/e2e_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 8e980152e3..12a4973808 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -51,6 +51,13 @@ func TestE2E(t *testing.T) { ) } + // Skip Dynamic Resolver test because DNS resolver doesn't work properly in IPV6 Github worker + if tests.IPFamily == "ipv6" { + skipTests = append(skipTests, + tests.DynamicResolverBackendTest.ShortName, + ) + } + cSuite, err := suite.NewConformanceTestSuite(suite.ConformanceOptions{ Client: c, RestConfig: cfg, From 3c8af6c6a02bbccc50a1a94f643d9ac0abff3e52 Mon Sep 17 00:00:00 2001 From: zirain Date: Thu, 8 May 2025 20:11:51 +0800 Subject: [PATCH 30/66] fix: proxy creation/deletion error handling in GatewayNamespace mode (#5954) * fix: proxy creation/deletion error handling in GatewayNamespace mode Signed-off-by: zirain * nit Signed-off-by: zirain * nit Signed-off-by: zirain * more nit Signed-off-by: zirain --------- Signed-off-by: zirain Signed-off-by: Arko Dasgupta --- internal/infrastructure/kubernetes/infra.go | 37 +++++++++-------- .../kubernetes/infra_resource.go | 28 ++++++------- .../kubernetes/proxy/resource_provider.go | 28 +++++++------ .../proxy/resource_provider_test.go | 40 ++++++++++++++++--- .../deployments/gateway-namespace-mode.yaml | 2 +- .../testdata/serviceaccount/default.yaml | 3 +- .../gateway-namespace-mode.yaml | 12 ++++++ .../serviceaccount/with-annotations.yaml | 3 +- .../infrastructure/kubernetes/proxy_infra.go | 21 +++++----- .../kubernetes/ratelimit/resource_provider.go | 22 +++++----- 10 files changed, 124 insertions(+), 72 deletions(-) create mode 100644 internal/infrastructure/kubernetes/proxy/testdata/serviceaccount/gateway-namespace-mode.yaml diff --git a/internal/infrastructure/kubernetes/infra.go b/internal/infrastructure/kubernetes/infra.go index 4ecbdc5b67..9c460775f2 100644 --- a/internal/infrastructure/kubernetes/infra.go +++ b/internal/infrastructure/kubernetes/infra.go @@ -31,6 +31,7 @@ var _ ResourceRender = &ratelimit.ResourceRender{} // based on Infra IR resources. type ResourceRender interface { Name() string + Namespace() string LabelSelector() labels.Selector ServiceAccount() (*corev1.ServiceAccount, error) Service() (*corev1.Service, error) @@ -61,12 +62,10 @@ type Infra struct { // NewInfra returns a new Infra. func NewInfra(cli client.Client, cfg *config.Server) *Infra { - var ns string - if !cfg.EnvoyGateway.GatewayNamespaceMode() { - ns = cfg.ControllerNamespace - } return &Infra{ - Namespace: ns, + // Always set infra namespace to cfg.ControllerNamespace, + // Otherwise RateLimit resource provider will failed to create/delete. + Namespace: cfg.ControllerNamespace, DNSDomain: cfg.DNSDomain, EnvoyGateway: cfg.EnvoyGateway, Client: New(cli), @@ -81,31 +80,31 @@ func (i *Infra) Close() error { return nil } // provided ResourceRender, if it doesn't exist and updates it if it does. func (i *Infra) createOrUpdate(ctx context.Context, r ResourceRender) error { if err := i.createOrUpdateServiceAccount(ctx, r); err != nil { - return fmt.Errorf("failed to create or update serviceaccount %s/%s: %w", i.Namespace, r.Name(), err) + return fmt.Errorf("failed to create or update serviceaccount %s/%s: %w", r.Namespace(), r.Name(), err) } if err := i.createOrUpdateConfigMap(ctx, r); err != nil { - return fmt.Errorf("failed to create or update configmap %s/%s: %w", i.Namespace, r.Name(), err) + return fmt.Errorf("failed to create or update configmap %s/%s: %w", r.Namespace(), r.Name(), err) } if err := i.createOrUpdateDeployment(ctx, r); err != nil { - return fmt.Errorf("failed to create or update deployment %s/%s: %w", i.Namespace, r.Name(), err) + return fmt.Errorf("failed to create or update deployment %s/%s: %w", r.Namespace(), r.Name(), err) } if err := i.createOrUpdateDaemonSet(ctx, r); err != nil { - return fmt.Errorf("failed to create or update daemonset %s/%s: %w", i.Namespace, r.Name(), err) + return fmt.Errorf("failed to create or update daemonset %s/%s: %w", r.Namespace(), r.Name(), err) } if err := i.createOrUpdateService(ctx, r); err != nil { - return fmt.Errorf("failed to create or update service %s/%s: %w", i.Namespace, r.Name(), err) + return fmt.Errorf("failed to create or update service %s/%s: %w", r.Namespace(), r.Name(), err) } if err := i.createOrUpdateHPA(ctx, r); err != nil { - return fmt.Errorf("failed to create or update hpa %s/%s: %w", i.Namespace, r.Name(), err) + return fmt.Errorf("failed to create or update hpa %s/%s: %w", r.Namespace(), r.Name(), err) } if err := i.createOrUpdatePodDisruptionBudget(ctx, r); err != nil { - return fmt.Errorf("failed to create or update pdb %s/%s: %w", i.Namespace, r.Name(), err) + return fmt.Errorf("failed to create or update pdb %s/%s: %w", r.Namespace(), r.Name(), err) } return nil @@ -114,31 +113,31 @@ func (i *Infra) createOrUpdate(ctx context.Context, r ResourceRender) error { // delete deletes the ServiceAccount/ConfigMap/Deployment/Service in the kube api server, if it exists. func (i *Infra) delete(ctx context.Context, r ResourceRender) error { if err := i.deleteServiceAccount(ctx, r); err != nil { - return fmt.Errorf("failed to delete serviceaccount %s/%s: %w", i.Namespace, r.Name(), err) + return fmt.Errorf("failed to delete serviceaccount %s/%s: %w", r.Namespace(), r.Name(), err) } if err := i.deleteConfigMap(ctx, r); err != nil { - return fmt.Errorf("failed to delete configmap %s/%s: %w", i.Namespace, r.Name(), err) + return fmt.Errorf("failed to delete configmap %s/%s: %w", r.Namespace(), r.Name(), err) } if err := i.deleteDeployment(ctx, r); err != nil { - return fmt.Errorf("failed to delete deployment %s/%s: %w", i.Namespace, r.Name(), err) + return fmt.Errorf("failed to delete deployment %s/%s: %w", r.Namespace(), r.Name(), err) } if err := i.deleteDaemonSet(ctx, r); err != nil { - return fmt.Errorf("failed to delete daemonset %s/%s: %w", i.Namespace, r.Name(), err) + return fmt.Errorf("failed to delete daemonset %s/%s: %w", r.Namespace(), r.Name(), err) } if err := i.deleteService(ctx, r); err != nil { - return fmt.Errorf("failed to delete service %s/%s: %w", i.Namespace, r.Name(), err) + return fmt.Errorf("failed to delete service %s/%s: %w", r.Namespace(), r.Name(), err) } if err := i.deleteHPA(ctx, r); err != nil { - return fmt.Errorf("failed to delete hpa %s/%s: %w", i.Namespace, r.Name(), err) + return fmt.Errorf("failed to delete hpa %s/%s: %w", r.Namespace(), r.Name(), err) } if err := i.deletePDB(ctx, r); err != nil { - return fmt.Errorf("failed to delete pdb %s/%s: %w", i.Namespace, r.Name(), err) + return fmt.Errorf("failed to delete pdb %s/%s: %w", r.Namespace(), r.Name(), err) } return nil diff --git a/internal/infrastructure/kubernetes/infra_resource.go b/internal/infrastructure/kubernetes/infra_resource.go index c37bd81f5e..018e5e00fa 100644 --- a/internal/infrastructure/kubernetes/infra_resource.go +++ b/internal/infrastructure/kubernetes/infra_resource.go @@ -33,7 +33,7 @@ func (i *Infra) createOrUpdateServiceAccount(ctx context.Context, r ResourceRend labels = []metrics.LabelValue{ kindLabel.Value("ServiceAccount"), nameLabel.Value(r.Name()), - namespaceLabel.Value(i.Namespace), + namespaceLabel.Value(r.Namespace()), } ) @@ -68,7 +68,7 @@ func (i *Infra) createOrUpdateConfigMap(ctx context.Context, r ResourceRender) ( labels = []metrics.LabelValue{ kindLabel.Value("ConfigMap"), nameLabel.Value(r.Name()), - namespaceLabel.Value(i.Namespace), + namespaceLabel.Value(r.Namespace()), } ) @@ -102,7 +102,7 @@ func (i *Infra) createOrUpdateDeployment(ctx context.Context, r ResourceRender) labels = []metrics.LabelValue{ kindLabel.Value("Deployment"), nameLabel.Value(r.Name()), - namespaceLabel.Value(i.Namespace), + namespaceLabel.Value(r.Namespace()), } ) @@ -189,7 +189,7 @@ func (i *Infra) createOrUpdateDaemonSet(ctx context.Context, r ResourceRender) ( labels = []metrics.LabelValue{ kindLabel.Value("DaemonSet"), nameLabel.Value(r.Name()), - namespaceLabel.Value(i.Namespace), + namespaceLabel.Value(r.Namespace()), } ) @@ -282,7 +282,7 @@ func (i *Infra) createOrUpdatePodDisruptionBudget(ctx context.Context, r Resourc labels = []metrics.LabelValue{ kindLabel.Value("PDB"), nameLabel.Value(r.Name()), - namespaceLabel.Value(i.Namespace), + namespaceLabel.Value(r.Namespace()), } ) @@ -319,7 +319,7 @@ func (i *Infra) createOrUpdateHPA(ctx context.Context, r ResourceRender) (err er labels = []metrics.LabelValue{ kindLabel.Value("HPA"), nameLabel.Value(r.Name()), - namespaceLabel.Value(i.Namespace), + namespaceLabel.Value(r.Namespace()), } ) @@ -355,7 +355,7 @@ func (i *Infra) createOrUpdateService(ctx context.Context, r ResourceRender) (er labels = []metrics.LabelValue{ kindLabel.Value("Service"), nameLabel.Value(r.Name()), - namespaceLabel.Value(i.Namespace), + namespaceLabel.Value(r.Namespace()), } ) @@ -390,7 +390,7 @@ func (i *Infra) createOrUpdateService(ctx context.Context, r ResourceRender) (er // deleteServiceAccount deletes the ServiceAccount in the kube api server, if it exists. func (i *Infra) deleteServiceAccount(ctx context.Context, r ResourceRender) (err error) { var ( - name, ns = r.Name(), i.Namespace + name, ns = r.Name(), r.Namespace() sa = &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ Namespace: ns, @@ -425,7 +425,7 @@ func (i *Infra) deleteServiceAccount(ctx context.Context, r ResourceRender) (err // deleteDeployment deletes the Envoy Deployment in the kube api server, if it exists. func (i *Infra) deleteDeployment(ctx context.Context, r ResourceRender) (err error) { var ( - name, ns = r.Name(), i.Namespace + name, ns = r.Name(), r.Namespace() deployment = &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Namespace: ns, @@ -460,7 +460,7 @@ func (i *Infra) deleteDeployment(ctx context.Context, r ResourceRender) (err err // deleteDaemonSet deletes the Envoy DaemonSet in the kube api server, if it exists. func (i *Infra) deleteDaemonSet(ctx context.Context, r ResourceRender) (err error) { var ( - name, ns = r.Name(), i.Namespace + name, ns = r.Name(), r.Namespace() daemonSet = &appsv1.DaemonSet{ ObjectMeta: metav1.ObjectMeta{ Namespace: ns, @@ -495,7 +495,7 @@ func (i *Infra) deleteDaemonSet(ctx context.Context, r ResourceRender) (err erro // deleteConfigMap deletes the ConfigMap in the kube api server, if it exists. func (i *Infra) deleteConfigMap(ctx context.Context, r ResourceRender) (err error) { var ( - name, ns = r.Name(), i.Namespace + name, ns = r.Name(), r.Namespace() cm = &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Namespace: ns, @@ -530,7 +530,7 @@ func (i *Infra) deleteConfigMap(ctx context.Context, r ResourceRender) (err erro // deleteService deletes the Service in the kube api server, if it exists. func (i *Infra) deleteService(ctx context.Context, r ResourceRender) (err error) { var ( - name, ns = r.Name(), i.Namespace + name, ns = r.Name(), r.Namespace() svc = &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Namespace: ns, @@ -565,7 +565,7 @@ func (i *Infra) deleteService(ctx context.Context, r ResourceRender) (err error) // deleteHpa deletes the Horizontal Pod Autoscaler associated to its renderer, if it exists. func (i *Infra) deleteHPA(ctx context.Context, r ResourceRender) (err error) { var ( - name, ns = r.Name(), i.Namespace + name, ns = r.Name(), r.Namespace() hpa = &autoscalingv2.HorizontalPodAutoscaler{ ObjectMeta: metav1.ObjectMeta{ Namespace: ns, @@ -600,7 +600,7 @@ func (i *Infra) deleteHPA(ctx context.Context, r ResourceRender) (err error) { // deletePDB deletes the PodDistribution budget associated to its renderer, if it exists. func (i *Infra) deletePDB(ctx context.Context, r ResourceRender) (err error) { var ( - name, ns = r.Name(), i.Namespace + name, ns = r.Name(), r.Namespace() pdb = &policyv1.PodDisruptionBudget{ ObjectMeta: metav1.ObjectMeta{ Namespace: ns, diff --git a/internal/infrastructure/kubernetes/proxy/resource_provider.go b/internal/infrastructure/kubernetes/proxy/resource_provider.go index ca4543d072..0c8b5e16d6 100644 --- a/internal/infrastructure/kubernetes/proxy/resource_provider.go +++ b/internal/infrastructure/kubernetes/proxy/resource_provider.go @@ -46,8 +46,8 @@ const ( type ResourceRender struct { infra *ir.ProxyInfra - // Namespace is the Namespace used for managed infra. - Namespace string + // namespace is the Namespace used for managed infra. + namespace string // DNSDomain is the dns domain used by k8s services. Defaults to "cluster.local". DNSDomain string @@ -59,7 +59,7 @@ type ResourceRender struct { func NewResourceRender(ns, dnsDomain string, infra *ir.ProxyInfra, gateway *egv1a1.EnvoyGateway) *ResourceRender { return &ResourceRender{ - Namespace: ns, + namespace: ns, DNSDomain: dnsDomain, infra: infra, ShutdownManager: gateway.GetEnvoyGatewayProvider().GetEnvoyGatewayKubeProvider().ShutdownManager, @@ -71,6 +71,10 @@ func (r *ResourceRender) Name() string { return ExpectedResourceHashedName(r.infra.Name) } +func (r *ResourceRender) Namespace() string { + return r.namespace +} + func (r *ResourceRender) LabelSelector() labels.Selector { return labels.SelectorFromSet(r.stableSelector().MatchLabels) } @@ -89,7 +93,7 @@ func (r *ResourceRender) ServiceAccount() (*corev1.ServiceAccount, error) { APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ - Namespace: r.Namespace, + Namespace: r.Namespace(), Name: r.Name(), Labels: labels, Annotations: r.infra.GetProxyMetadata().Annotations, @@ -196,7 +200,7 @@ func (r *ResourceRender) Service() (*corev1.Service, error) { Kind: "Service", }, ObjectMeta: metav1.ObjectMeta{ - Namespace: r.Namespace, + Namespace: r.Namespace(), Labels: svcLabels, Annotations: annotations, }, @@ -241,7 +245,7 @@ func (r *ResourceRender) ConfigMap(cert string) (*corev1.ConfigMap, error) { APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ - Namespace: r.Namespace, + Namespace: r.Namespace(), Name: r.Name(), Labels: labels, Annotations: r.infra.GetProxyMetadata().Annotations, @@ -280,7 +284,7 @@ func (r *ResourceRender) Deployment() (*appsv1.Deployment, error) { } // Get expected bootstrap configurations rendered ProxyContainers - containers, err := expectedProxyContainers(r.infra, deploymentConfig.Container, proxyConfig.Spec.Shutdown, r.ShutdownManager, r.Namespace, r.DNSDomain, r.GatewayNamespaceMode) + containers, err := expectedProxyContainers(r.infra, deploymentConfig.Container, proxyConfig.Spec.Shutdown, r.ShutdownManager, r.Namespace(), r.DNSDomain, r.GatewayNamespaceMode) if err != nil { return nil, err } @@ -300,7 +304,7 @@ func (r *ResourceRender) Deployment() (*appsv1.Deployment, error) { APIVersion: "apps/v1", }, ObjectMeta: metav1.ObjectMeta{ - Namespace: r.Namespace, + Namespace: r.Namespace(), Labels: dpLabels, Annotations: dpAnnotations, }, @@ -369,7 +373,7 @@ func (r *ResourceRender) DaemonSet() (*appsv1.DaemonSet, error) { } // Get expected bootstrap configurations rendered ProxyContainers - containers, err := expectedProxyContainers(r.infra, daemonSetConfig.Container, proxyConfig.Spec.Shutdown, r.ShutdownManager, r.Namespace, r.DNSDomain, r.GatewayNamespaceMode) + containers, err := expectedProxyContainers(r.infra, daemonSetConfig.Container, proxyConfig.Spec.Shutdown, r.ShutdownManager, r.Namespace(), r.DNSDomain, r.GatewayNamespaceMode) if err != nil { return nil, err } @@ -389,7 +393,7 @@ func (r *ResourceRender) DaemonSet() (*appsv1.DaemonSet, error) { APIVersion: "apps/v1", }, ObjectMeta: metav1.ObjectMeta{ - Namespace: r.Namespace, + Namespace: r.Namespace(), Labels: dsLabels, Annotations: dsAnnotations, }, @@ -458,7 +462,7 @@ func (r *ResourceRender) PodDisruptionBudget() (*policyv1.PodDisruptionBudget, e podDisruptionBudget := &policyv1.PodDisruptionBudget{ ObjectMeta: metav1.ObjectMeta{ Name: r.Name(), - Namespace: r.Namespace, + Namespace: r.Namespace(), }, TypeMeta: metav1.TypeMeta{ APIVersion: "policy/v1", @@ -492,7 +496,7 @@ func (r *ResourceRender) HorizontalPodAutoscaler() (*autoscalingv2.HorizontalPod Kind: "HorizontalPodAutoscaler", }, ObjectMeta: metav1.ObjectMeta{ - Namespace: r.Namespace, + Namespace: r.Namespace(), Name: r.Name(), Annotations: r.infra.GetProxyMetadata().Annotations, Labels: r.infra.GetProxyMetadata().Labels, diff --git a/internal/infrastructure/kubernetes/proxy/resource_provider_test.go b/internal/infrastructure/kubernetes/proxy/resource_provider_test.go index 0651a433af..88899754de 100644 --- a/internal/infrastructure/kubernetes/proxy/resource_provider_test.go +++ b/internal/infrastructure/kubernetes/proxy/resource_provider_test.go @@ -567,7 +567,7 @@ func TestDeployment(t *testing.T) { }, { caseName: "gateway-namespace-mode", - infra: newTestInfraWithNamespace("default"), + infra: newTestInfraWithNamespace("ns1"), gatewayNamespaceMode: true, }, } @@ -1291,27 +1291,57 @@ func TestServiceAccount(t *testing.T) { cfg, err := config.New(os.Stdout) require.NoError(t, err) cases := []struct { - name string - infra *ir.Infra + name string + infra *ir.Infra + gatewayNamespaceMode bool }{ { name: "default", infra: newTestInfra(), - }, { + }, + { name: "with-annotations", infra: newTestInfraWithAnnotations(map[string]string{ "anno1": "value1", "anno2": "value2", }), }, + { + name: "gateway-namespace-mode", + infra: newTestInfraWithNamespace("ns1"), + gatewayNamespaceMode: true, + }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - r := NewResourceRender(cfg.ControllerNamespace, cfg.DNSDomain, tc.infra.GetProxyInfra(), cfg.EnvoyGateway) + ns := cfg.ControllerNamespace + if tc.gatewayNamespaceMode { + deployType := egv1a1.KubernetesDeployModeType(egv1a1.KubernetesDeployModeTypeGatewayNamespace) + cfg.EnvoyGateway.Provider = &egv1a1.EnvoyGatewayProvider{ + Type: egv1a1.ProviderTypeKubernetes, + Kubernetes: &egv1a1.EnvoyGatewayKubernetesProvider{ + Deploy: &egv1a1.KubernetesDeployMode{ + Type: &deployType, + }, + }, + } + ns = tc.infra.GetProxyInfra().Namespace + } + r := NewResourceRender(ns, cfg.DNSDomain, tc.infra.GetProxyInfra(), cfg.EnvoyGateway) + sa, err := r.ServiceAccount() require.NoError(t, err) + if test.OverrideTestData() { + saYAML, err := yaml.Marshal(sa) + require.NoError(t, err) + // nolint: gosec + err = os.WriteFile(fmt.Sprintf("testdata/serviceaccount/%s.yaml", tc.name), saYAML, 0o644) + require.NoError(t, err) + return + } + expected, err := loadServiceAccount(tc.name) require.NoError(t, err) diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/gateway-namespace-mode.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/gateway-namespace-mode.yaml index 403c4a2d79..2d8a29078f 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/gateway-namespace-mode.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/gateway-namespace-mode.yaml @@ -9,7 +9,7 @@ metadata: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default name: envoy-default-37a8eec1 - namespace: default + namespace: ns1 spec: progressDeadlineSeconds: 600 revisionHistoryLimit: 10 diff --git a/internal/infrastructure/kubernetes/proxy/testdata/serviceaccount/default.yaml b/internal/infrastructure/kubernetes/proxy/testdata/serviceaccount/default.yaml index 4e2731766a..04760e0b91 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/serviceaccount/default.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/serviceaccount/default.yaml @@ -1,10 +1,11 @@ apiVersion: v1 kind: ServiceAccount metadata: + creationTimestamp: null labels: - app.kubernetes.io/name: envoy app.kubernetes.io/component: proxy app.kubernetes.io/managed-by: envoy-gateway + app.kubernetes.io/name: envoy gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default name: envoy-default-37a8eec1 diff --git a/internal/infrastructure/kubernetes/proxy/testdata/serviceaccount/gateway-namespace-mode.yaml b/internal/infrastructure/kubernetes/proxy/testdata/serviceaccount/gateway-namespace-mode.yaml new file mode 100644 index 0000000000..ac3b47bb81 --- /dev/null +++ b/internal/infrastructure/kubernetes/proxy/testdata/serviceaccount/gateway-namespace-mode.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + creationTimestamp: null + labels: + app.kubernetes.io/component: proxy + app.kubernetes.io/managed-by: envoy-gateway + app.kubernetes.io/name: envoy + gateway.envoyproxy.io/owning-gateway-name: default + gateway.envoyproxy.io/owning-gateway-namespace: default + name: envoy-default-37a8eec1 + namespace: ns1 diff --git a/internal/infrastructure/kubernetes/proxy/testdata/serviceaccount/with-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/serviceaccount/with-annotations.yaml index f63c97451c..f4076de4e1 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/serviceaccount/with-annotations.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/serviceaccount/with-annotations.yaml @@ -4,10 +4,11 @@ metadata: annotations: anno1: value1 anno2: value2 + creationTimestamp: null labels: - app.kubernetes.io/name: envoy app.kubernetes.io/component: proxy app.kubernetes.io/managed-by: envoy-gateway + app.kubernetes.io/name: envoy gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default name: envoy-default-37a8eec1 diff --git a/internal/infrastructure/kubernetes/proxy_infra.go b/internal/infrastructure/kubernetes/proxy_infra.go index 080271a6bd..f911e5273e 100644 --- a/internal/infrastructure/kubernetes/proxy_infra.go +++ b/internal/infrastructure/kubernetes/proxy_infra.go @@ -23,11 +23,8 @@ func (i *Infra) CreateOrUpdateProxyInfra(ctx context.Context, infra *ir.Infra) e return errors.New("infra proxy ir is nil") } - if i.EnvoyGateway.GatewayNamespaceMode() && i.Namespace == "" { - i.Namespace = infra.Proxy.Namespace - } - - r := proxy.NewResourceRender(i.Namespace, i.DNSDomain, infra.GetProxyInfra(), i.EnvoyGateway) + ns := i.GetResourceNamespace(infra) + r := proxy.NewResourceRender(ns, i.DNSDomain, infra.GetProxyInfra(), i.EnvoyGateway) return i.createOrUpdate(ctx, r) } @@ -37,10 +34,14 @@ func (i *Infra) DeleteProxyInfra(ctx context.Context, infra *ir.Infra) error { return errors.New("infra ir is nil") } - if i.EnvoyGateway.GatewayNamespaceMode() && i.Namespace == "" { - i.Namespace = infra.Proxy.Namespace - } - - r := proxy.NewResourceRender(i.Namespace, i.DNSDomain, infra.GetProxyInfra(), i.EnvoyGateway) + ns := i.GetResourceNamespace(infra) + r := proxy.NewResourceRender(ns, i.DNSDomain, infra.GetProxyInfra(), i.EnvoyGateway) return i.delete(ctx, r) } + +func (i *Infra) GetResourceNamespace(infra *ir.Infra) string { + if i.EnvoyGateway.GatewayNamespaceMode() { + return infra.Proxy.Namespace + } + return i.Namespace +} diff --git a/internal/infrastructure/kubernetes/ratelimit/resource_provider.go b/internal/infrastructure/kubernetes/ratelimit/resource_provider.go index 66219636ce..9013127e0f 100644 --- a/internal/infrastructure/kubernetes/ratelimit/resource_provider.go +++ b/internal/infrastructure/kubernetes/ratelimit/resource_provider.go @@ -38,8 +38,8 @@ const ( var statsConf string type ResourceRender struct { - // Namespace is the Namespace used for managed infra. - Namespace string + // namespace is the Namespace used for managed infra. + namespace string rateLimit *egv1a1.RateLimit rateLimitDeployment *egv1a1.KubernetesDeploymentSpec @@ -52,7 +52,7 @@ type ResourceRender struct { // NewResourceRender returns a new ResourceRender. func NewResourceRender(ns string, gateway *egv1a1.EnvoyGateway, ownerReferenceUID map[string]types.UID) *ResourceRender { return &ResourceRender{ - Namespace: ns, + namespace: ns, rateLimit: gateway.RateLimit, rateLimitDeployment: gateway.GetEnvoyGatewayProvider().GetEnvoyGatewayKubeProvider().RateLimitDeployment, rateLimitHpa: gateway.GetEnvoyGatewayProvider().GetEnvoyGatewayKubeProvider().RateLimitHpa, @@ -64,6 +64,10 @@ func (r *ResourceRender) Name() string { return InfraName } +func (r *ResourceRender) Namespace() string { + return r.namespace +} + func (r *ResourceRender) LabelSelector() labels.Selector { return labels.SelectorFromSet(rateLimitLabels()) } @@ -91,7 +95,7 @@ func (r *ResourceRender) ConfigMap(cert string) (*corev1.ConfigMap, error) { APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ - Namespace: r.Namespace, + Namespace: r.Namespace(), Name: "statsd-exporter-config", Labels: rateLimitLabels(), }, @@ -138,7 +142,7 @@ func (r *ResourceRender) Service() (*corev1.Service, error) { APIVersion: apiVersion, }, ObjectMeta: metav1.ObjectMeta{ - Namespace: r.Namespace, + Namespace: r.Namespace(), Name: InfraName, Labels: labels, }, @@ -171,7 +175,7 @@ func (r *ResourceRender) ServiceAccount() (*corev1.ServiceAccount, error) { APIVersion: apiVersion, }, ObjectMeta: metav1.ObjectMeta{ - Namespace: r.Namespace, + Namespace: r.Namespace(), Name: InfraName, }, } @@ -194,7 +198,7 @@ func (r *ResourceRender) ServiceAccount() (*corev1.ServiceAccount, error) { // Deployment returns the expected rate limit Deployment based on the provided infra. func (r *ResourceRender) Deployment() (*appsv1.Deployment, error) { - containers := expectedRateLimitContainers(r.rateLimit, r.rateLimitDeployment, r.Namespace) + containers := expectedRateLimitContainers(r.rateLimit, r.rateLimitDeployment, r.Namespace()) selector := resource.GetSelector(rateLimitLabels()) podLabels := rateLimitLabels() @@ -227,7 +231,7 @@ func (r *ResourceRender) Deployment() (*appsv1.Deployment, error) { APIVersion: appsAPIVersion, }, ObjectMeta: metav1.ObjectMeta{ - Namespace: r.Namespace, + Namespace: r.Namespace(), Labels: rateLimitLabels(), }, Spec: appsv1.DeploymentSpec{ @@ -313,7 +317,7 @@ func (r *ResourceRender) HorizontalPodAutoscaler() (*autoscalingv2.HorizontalPod Kind: "HorizontalPodAutoscaler", }, ObjectMeta: metav1.ObjectMeta{ - Namespace: r.Namespace, + Namespace: r.Namespace(), Name: r.Name(), Labels: rateLimitLabels(), }, From f5d1266ed6b17eed0ee075773184fb812a3432c6 Mon Sep 17 00:00:00 2001 From: zirain Date: Thu, 8 May 2025 20:46:34 +0800 Subject: [PATCH 31/66] ci: kube-deploy support KUBE_DEPLOY_PROFILE (#5957) * ci: kube-deploy support helm values configuration file Signed-off-by: zirain * move to test/cofnig Signed-off-by: zirain * fix Signed-off-by: zirain --------- Signed-off-by: zirain Signed-off-by: Arko Dasgupta --- .github/workflows/build_and_test.yaml | 3 ++- .../default.yaml} | 0 .../gateway-namespace-mode.yaml | 24 +++++++++++++++++++ test/config/helm/default.yaml | 1 + test/config/helm/gateway-namespace-mode.yaml | 9 +++++++ tools/make/kube.mk | 13 ++++++++-- 6 files changed, 47 insertions(+), 3 deletions(-) rename test/config/{envoy-gateway-config.yaml => envoy-gateaway-config/default.yaml} (100%) create mode 100644 test/config/envoy-gateaway-config/gateway-namespace-mode.yaml create mode 100644 test/config/helm/default.yaml create mode 100644 test/config/helm/gateway-namespace-mode.yaml diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml index 2c3d635fad..200e7d5718 100644 --- a/.github/workflows/build_and_test.yaml +++ b/.github/workflows/build_and_test.yaml @@ -187,6 +187,7 @@ jobs: - name: Read Benchmark report run: cat test/benchmark/benchmark_report/benchmark_report.md + resilience-test: runs-on: ubuntu-latest if: ${{ ! startsWith(github.event_name, 'push') }} @@ -203,7 +204,7 @@ jobs: publish: runs-on: ubuntu-latest - needs: [conformance-test, e2e-test] + needs: [conformance-test, e2e-test, resilience-test] steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: ./tools/github-actions/setup-deps diff --git a/test/config/envoy-gateway-config.yaml b/test/config/envoy-gateaway-config/default.yaml similarity index 100% rename from test/config/envoy-gateway-config.yaml rename to test/config/envoy-gateaway-config/default.yaml diff --git a/test/config/envoy-gateaway-config/gateway-namespace-mode.yaml b/test/config/envoy-gateaway-config/gateway-namespace-mode.yaml new file mode 100644 index 0000000000..a467c274b2 --- /dev/null +++ b/test/config/envoy-gateaway-config/gateway-namespace-mode.yaml @@ -0,0 +1,24 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: envoy-gateway-config + namespace: envoy-gateway-system +data: + envoy-gateway.yaml: | + apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyGateway + provider: + type: Kubernetes + kubernetes: + deploy: + type: GatewayNamespace + gateway: + controllerName: gateway.envoyproxy.io/gatewayclass-controller + extensionApis: + enableEnvoyPatchPolicy: true + enableBackend: true + rateLimit: + backend: + type: Redis + redis: + url: redis.redis-system.svc.cluster.local:6379 diff --git a/test/config/helm/default.yaml b/test/config/helm/default.yaml new file mode 100644 index 0000000000..2503344382 --- /dev/null +++ b/test/config/helm/default.yaml @@ -0,0 +1 @@ +# the default configuration for the gateway helm chart diff --git a/test/config/helm/gateway-namespace-mode.yaml b/test/config/helm/gateway-namespace-mode.yaml new file mode 100644 index 0000000000..7694f06211 --- /dev/null +++ b/test/config/helm/gateway-namespace-mode.yaml @@ -0,0 +1,9 @@ +config: + envoyGateway: + gateway: + controllerName: gateway.envoyproxy.io/gatewayclass-controller + provider: + type: Kubernetes + kubernetes: + deploy: + type: GatewayNamespace diff --git a/tools/make/kube.mk b/tools/make/kube.mk index ef5311cd2c..020e4da744 100644 --- a/tools/make/kube.mk +++ b/tools/make/kube.mk @@ -28,6 +28,10 @@ E2E_CLEANUP ?= true E2E_TIMEOUT ?= 20m E2E_TEST_ARGS ?= -v -tags e2e -timeout $(E2E_TIMEOUT) +KUBE_DEPLOY_PROFILE ?= default +KUBE_DEPLOY_HELM_VALUES_FILE = $(ROOT_DIR)/test/config/helm/$(KUBE_DEPLOY_PROFILE).yaml +KUBE_DEPLOY_EG_CONFIG_FILE = $(ROOT_DIR)/test/config/envoy-gateaway-config/$(KUBE_DEPLOY_PROFILE).yaml + # Set Kubernetes Resources Directory Path ifeq ($(origin KUBE_PROVIDER_DIR),undefined) KUBE_PROVIDER_DIR := $(ROOT_DIR)/internal/provider/kubernetes/config @@ -105,7 +109,12 @@ endif .PHONY: kube-deploy kube-deploy: manifests helm-generate.gateway-helm ## Install Envoy Gateway into the Kubernetes cluster specified in ~/.kube/config. @$(LOG_TARGET) - helm install eg charts/gateway-helm --set deployment.envoyGateway.imagePullPolicy=$(IMAGE_PULL_POLICY) -n envoy-gateway-system --create-namespace --debug --timeout='$(WAIT_TIMEOUT)' --wait --wait-for-jobs + helm install eg charts/gateway-helm \ + --set deployment.envoyGateway.imagePullPolicy=$(IMAGE_PULL_POLICY) \ + -n envoy-gateway-system --create-namespace \ + --debug --timeout='$(WAIT_TIMEOUT)' \ + --wait --wait-for-jobs \ + -f $(KUBE_DEPLOY_HELM_VALUES_FILE) .PHONY: kube-deploy-for-benchmark-test kube-deploy-for-benchmark-test: manifests helm-generate ## Install Envoy Gateway and prometheus-server for benchmark test purpose only. @@ -192,7 +201,7 @@ e2e-prepare: prepare-ip-family ## Prepare the environment for running e2e tests @$(LOG_TARGET) kubectl wait --timeout=5m -n envoy-gateway-system deployment/envoy-ratelimit --for=condition=Available kubectl wait --timeout=5m -n envoy-gateway-system deployment/envoy-gateway --for=condition=Available - kubectl apply -f test/config/envoy-gateway-config.yaml + kubectl apply -f $(KUBE_DEPLOY_EG_CONFIG_FILE) kubectl apply -f test/config/gatewayclass.yaml .PHONY: run-e2e From 50b328e6ffbdd12fd2f222caaa3792172c0df7bf Mon Sep 17 00:00:00 2001 From: Arko Dasgupta Date: Thu, 8 May 2025 10:06:57 -0700 Subject: [PATCH 32/66] fix: process remaining gatewayClasses after encountering an err (#5953) fix: process all gatewayClasses after encountering an err * instead of returning from Reconcile after encountering an err which processing a `GatewayClass`, `continue` instead to process all GatewayClasses Fixes: https://github.com/envoyproxy/gateway/issues/5618 Signed-off-by: Arko Dasgupta --- internal/provider/kubernetes/controller.go | 45 +++++++++++++--------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/internal/provider/kubernetes/controller.go b/internal/provider/kubernetes/controller.go index 897b030dcf..b4eb87c0f3 100644 --- a/internal/provider/kubernetes/controller.go +++ b/internal/provider/kubernetes/controller.go @@ -228,63 +228,72 @@ func (r *gatewayAPIReconciler) Reconcile(ctx context.Context, _ reconcile.Reques string(gwapiv1.GatewayClassReasonInvalidParameters), msg) r.resources.GatewayClassStatuses.Store(utils.NamespacedName(gc), &gc.Status) - return reconcile.Result{}, nil + continue } } // Add all Gateways, their associated Routes, and referenced resources to the resourceTree if err = r.processGateways(ctx, managedGC, resourceMappings, gwcResource); err != nil { - return reconcile.Result{}, err + r.log.Error(err, fmt.Sprintf("failed processGateways for gatewayClass %s, skipping it", managedGC.Name)) + continue } if r.eppCRDExists { // Add all EnvoyPatchPolicies to the resourceTree if err = r.processEnvoyPatchPolicies(ctx, gwcResource, resourceMappings); err != nil { - return reconcile.Result{}, err + r.log.Error(err, fmt.Sprintf("failed processEnvoyPatchPolicies for gatewayClass %s, skipping it", managedGC.Name)) + continue } } if r.ctpCRDExists { // Add all ClientTrafficPolicies and their referenced resources to the resourceTree if err = r.processClientTrafficPolicies(ctx, gwcResource, resourceMappings); err != nil { - return reconcile.Result{}, err + r.log.Error(err, fmt.Sprintf("failed processClientTrafficPolicies for gatewayClass %s, skipping it", managedGC.Name)) + continue } } if r.btpCRDExists { // Add all BackendTrafficPolicies to the resourceTree if err = r.processBackendTrafficPolicies(ctx, gwcResource, resourceMappings); err != nil { - return reconcile.Result{}, err + r.log.Error(err, fmt.Sprintf("failed processBackendTrafficPolicies for gatewayClass %s, skipping it", managedGC.Name)) + continue } } if r.spCRDExists { // Add all SecurityPolicies and their referenced resources to the resourceTree if err = r.processSecurityPolicies(ctx, gwcResource, resourceMappings); err != nil { - return reconcile.Result{}, err + r.log.Error(err, fmt.Sprintf("failed processSecurityPolicies for gatewayClass %s, skipping it", managedGC.Name)) + continue } } if r.bTLSPolicyCRDExists { // Add all BackendTLSPolies to the resourceTree if err = r.processBackendTLSPolicies(ctx, gwcResource, resourceMappings); err != nil { - return reconcile.Result{}, err + r.log.Error(err, fmt.Sprintf("failed processBackendTLSPolicies for gatewayClass %s, skipping it", managedGC.Name)) + continue } } if r.eepCRDExists { // Add all EnvoyExtensionPolicies and their referenced resources to the resourceTree if err = r.processEnvoyExtensionPolicies(ctx, gwcResource, resourceMappings); err != nil { - return reconcile.Result{}, err + r.log.Error(err, fmt.Sprintf("failed processEnvoyExtensionPolicies for gatewayClass %s, skipping it", managedGC.Name)) + continue } } if err = r.processExtensionServerPolicies(ctx, gwcResource); err != nil { - return reconcile.Result{}, err + r.log.Error(err, fmt.Sprintf("failed processExtensionServerPolicies for gatewayClass %s, skipping it", managedGC.Name)) + continue } if r.backendCRDExists { if err = r.processBackends(ctx, gwcResource); err != nil { - return reconcile.Result{}, err + r.log.Error(err, fmt.Sprintf("failed processBackends for gatewayClass %s, skipping it", managedGC.Name)) + continue } } @@ -300,9 +309,9 @@ func (r *gatewayAPIReconciler) Reconcile(ctx context.Context, _ reconcile.Reques if err != nil { r.log.Error(err, "unable to find the namespace") if kerrors.IsNotFound(err) { - return reconcile.Result{}, nil + continue } - return reconcile.Result{}, err + continue } gwcResource.Namespaces = append(gwcResource.Namespaces, namespace) @@ -326,20 +335,20 @@ func (r *gatewayAPIReconciler) Reconcile(ctx context.Context, _ reconcile.Reques r.resources.GatewayClassStatuses.Store(utils.NamespacedName(gc), &gc.Status) if len(gwcResource.Gateways) == 0 { - r.log.Info("No gateways found for accepted gatewayclass") + r.log.Info("No gateways found for accepted gatewayClass") // If needed, remove the finalizer from the accepted GatewayClass. if err := r.removeFinalizer(ctx, managedGC); err != nil { - r.log.Error(err, fmt.Sprintf("failed to remove finalizer from gatewayclass %s", + r.log.Error(err, fmt.Sprintf("failed to remove finalizer from gatewayClass %s", managedGC.Name)) - return reconcile.Result{}, err + continue } } else { // finalize the accepted GatewayClass. if err := r.addFinalizer(ctx, managedGC); err != nil { - r.log.Error(err, fmt.Sprintf("failed adding finalizer to gatewayclass %s", + r.log.Error(err, fmt.Sprintf("failed adding finalizer to gatewayClass %s", managedGC.Name)) - return reconcile.Result{}, err + continue } } } @@ -1046,7 +1055,7 @@ func (r *gatewayAPIReconciler) processGateways(ctx context.Context, managedGC *g if err := r.client.List(ctx, gatewayList, &client.ListOptions{ FieldSelector: fields.OneTermEqualSelector(classGatewayIndex, managedGC.Name), }); err != nil { - r.log.Info("no associated Gateways found for GatewayClass", "name", managedGC.Name) + r.log.Error(err, "failed to list gateways for gatewayClass %s", managedGC.Name) return err } From 065fab842a790c498ea602203fffb4274e800302 Mon Sep 17 00:00:00 2001 From: Xunzhuo Date: Fri, 9 May 2025 01:25:41 +0800 Subject: [PATCH 33/66] fix: do not add tls inspector filter to quic listener (#5671) * fix: enable http3 but panic Signed-off-by: bitliu Signed-off-by: Arko Dasgupta --- internal/xds/translator/listener.go | 10 ++ .../xds/translator/server_names_match_test.go | 134 ++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 internal/xds/translator/server_names_match_test.go diff --git a/internal/xds/translator/listener.go b/internal/xds/translator/listener.go index bae4e0d0a5..78c09a7e03 100644 --- a/internal/xds/translator/listener.go +++ b/internal/xds/translator/listener.go @@ -517,6 +517,16 @@ func buildEarlyHeaderMutation(headers *ir.HeaderSettings) []*corev3.TypedExtensi } func addServerNamesMatch(xdsListener *listenerv3.Listener, filterChain *listenerv3.FilterChain, hostnames []string) error { + // Skip adding ServerNames match for: + // 1. nil listeners + // 2. UDP (QUIC) listeners used for HTTP3 + // 3. wildcard hostnames + if xdsListener == nil || (xdsListener.GetAddress() != nil && + xdsListener.GetAddress().GetSocketAddress() != nil && + xdsListener.GetAddress().GetSocketAddress().GetProtocol() == corev3.SocketAddress_UDP) { + return nil + } + // Dont add a filter chain match if the hostname is a wildcard character. if len(hostnames) > 0 && hostnames[0] != "*" { filterChain.FilterChainMatch = &listenerv3.FilterChainMatch{ diff --git a/internal/xds/translator/server_names_match_test.go b/internal/xds/translator/server_names_match_test.go new file mode 100644 index 0000000000..ce2f8b108b --- /dev/null +++ b/internal/xds/translator/server_names_match_test.go @@ -0,0 +1,134 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package translator + +import ( + "testing" + + corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" + "github.com/envoyproxy/go-control-plane/pkg/wellknown" + "github.com/stretchr/testify/require" +) + +func TestAddServerNamesMatch(t *testing.T) { + tests := []struct { + name string + xdsListener *listenerv3.Listener + hostnames []string + expectFilterChain bool + expectTLSInspector bool + expectServerNames []string + }{ + { + name: "nil listener", + xdsListener: nil, + hostnames: []string{"example.com"}, + expectFilterChain: false, + expectTLSInspector: false, + expectServerNames: nil, + }, + { + name: "UDP (QUIC) listener for HTTP3", + xdsListener: &listenerv3.Listener{ + Address: &corev3.Address{ + Address: &corev3.Address_SocketAddress{ + SocketAddress: &corev3.SocketAddress{ + Protocol: corev3.SocketAddress_UDP, + Address: "0.0.0.0", + PortSpecifier: &corev3.SocketAddress_PortValue{ + PortValue: 443, + }, + }, + }, + }, + }, + hostnames: []string{"example.com"}, + expectFilterChain: false, + expectTLSInspector: false, + expectServerNames: nil, + }, + { + name: "TCP listener with non-wildcard hostnames", + xdsListener: &listenerv3.Listener{ + Address: &corev3.Address{ + Address: &corev3.Address_SocketAddress{ + SocketAddress: &corev3.SocketAddress{ + Protocol: corev3.SocketAddress_TCP, + Address: "0.0.0.0", + PortSpecifier: &corev3.SocketAddress_PortValue{ + PortValue: 443, + }, + }, + }, + }, + }, + hostnames: []string{"example.com", "api.example.com"}, + expectFilterChain: true, + expectTLSInspector: true, + expectServerNames: []string{"example.com", "api.example.com"}, + }, + { + name: "TCP listener with wildcard hostname", + xdsListener: &listenerv3.Listener{ + Address: &corev3.Address{ + Address: &corev3.Address_SocketAddress{ + SocketAddress: &corev3.SocketAddress{ + Protocol: corev3.SocketAddress_TCP, + Address: "0.0.0.0", + PortSpecifier: &corev3.SocketAddress_PortValue{ + PortValue: 443, + }, + }, + }, + }, + }, + hostnames: []string{"*"}, + expectFilterChain: false, + expectTLSInspector: false, + expectServerNames: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + filterChain := &listenerv3.FilterChain{} + + err := addServerNamesMatch(tt.xdsListener, filterChain, tt.hostnames) + require.NoError(t, err) + + // Check if filter chain match was added + if tt.expectFilterChain { + require.NotNil(t, filterChain.FilterChainMatch) + require.Equal(t, tt.expectServerNames, filterChain.FilterChainMatch.ServerNames) + } else { + require.Nil(t, filterChain.FilterChainMatch) + } + + // Check if TLS inspector was added + if tt.xdsListener != nil && tt.expectTLSInspector { + hasTLSInspector := false + for _, filter := range tt.xdsListener.ListenerFilters { + if filter.Name == wellknown.TlsInspector { + hasTLSInspector = true + break + } + } + require.True(t, hasTLSInspector, "TLS inspector filter should be added") + } else if tt.xdsListener != nil { + // For non-nil listeners that shouldn't have TLS inspector + hasTLSInspector := false + for _, filter := range tt.xdsListener.ListenerFilters { + if filter.Name == wellknown.TlsInspector { + hasTLSInspector = true + break + } + } + require.False(t, hasTLSInspector, "TLS inspector filter should not be added") + } + }) + } +} From b5a023eaa145cd4c62b457bd66f35485f2826a9c Mon Sep 17 00:00:00 2001 From: Sudipto Baral Date: Thu, 8 May 2025 16:58:58 -0400 Subject: [PATCH 34/66] Add seed corpus related to traffic task. (#5947) * Add seed corpus related to traffic task. Signed-off-by: sudipto baral Signed-off-by: Arko Dasgupta --- .../testdata/FuzzGatewayAPIToXDS/api_key_auth | 25 +++ .../FuzzGatewayAPIToXDS/backend_routing | 46 ++++ .../testdata/FuzzGatewayAPIToXDS/basic_auth | 13 ++ .../FuzzGatewayAPIToXDS/circuit_breaker | 13 ++ .../FuzzGatewayAPIToXDS/client_traffic_policy | 84 ++++++++ .../testdata/FuzzGatewayAPIToXDS/configmap | 10 - .../FuzzGatewayAPIToXDS/connection_limit | 14 ++ test/fuzz/testdata/FuzzGatewayAPIToXDS/cors | 23 ++ .../FuzzGatewayAPIToXDS/direct_response | 65 ++++++ .../FuzzGatewayAPIToXDS/envoy_patch_policy | 31 +++ .../FuzzGatewayAPIToXDS/extension_server | 54 +++++ .../FuzzGatewayAPIToXDS/external_auth | 34 +++ .../testdata/FuzzGatewayAPIToXDS/failover | 201 ++++++++++++++++++ .../FuzzGatewayAPIToXDS/fault_injection | 69 ++++++ .../FuzzGatewayAPIToXDS/global_rate_limit | 71 +++++++ .../{gateway_and_grpcroute => grpc_routing} | 0 .../testdata/FuzzGatewayAPIToXDS/grpcroute | 22 -- .../FuzzGatewayAPIToXDS/http_redirect | 99 +++++++++ .../FuzzGatewayAPIToXDS/http_request_header | 52 +++++ .../FuzzGatewayAPIToXDS/http_response_header | 32 +++ .../{gateway_and_httproute => http_routing} | 0 .../testdata/FuzzGatewayAPIToXDS/http_timeout | 25 +++ .../http_traffic_splitting | 53 +++++ .../FuzzGatewayAPIToXDS/http_url_rewrite | 36 ++++ .../testdata/FuzzGatewayAPIToXDS/httproute | 14 -- .../testdata/FuzzGatewayAPIToXDS/ip_allowlist | 30 +++ .../testdata/FuzzGatewayAPIToXDS/jwt_auth | 56 +++++ .../FuzzGatewayAPIToXDS/jwt_claim_based_auth | 31 +++ .../FuzzGatewayAPIToXDS/load_balancing | 32 +++ .../FuzzGatewayAPIToXDS/local_ratelimit | 41 ++++ .../mtls_client_to_gateway | 17 ++ .../mtls_gateway_to_backend | 24 +++ .../FuzzGatewayAPIToXDS/multi_tenancy | 97 +++++++++ .../testdata/FuzzGatewayAPIToXDS/oidc_auth | 17 ++ .../FuzzGatewayAPIToXDS/proxy_metrics | 27 +++ .../FuzzGatewayAPIToXDS/proxy_tracing | 50 +++++ .../FuzzGatewayAPIToXDS/response_override | 43 ++++ test/fuzz/testdata/FuzzGatewayAPIToXDS/retry | 23 ++ .../FuzzGatewayAPIToXDS/secure_gateway | 17 ++ .../FuzzGatewayAPIToXDS/session_persistence | 42 ++++ .../{gateway_and_tcproute => tcp_routing} | 0 .../testdata/FuzzGatewayAPIToXDS/tcproute | 13 -- .../tls_gateway_to_backend | 35 +++ .../{gateway_and_udproute => udp_routing} | 0 .../testdata/FuzzGatewayAPIToXDS/udproute | 13 -- .../FuzzGatewayAPIToXDS/wasm_extension | 18 ++ 46 files changed, 1640 insertions(+), 72 deletions(-) create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/api_key_auth create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/backend_routing create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/basic_auth create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/circuit_breaker create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/client_traffic_policy delete mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/configmap create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/connection_limit create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/cors create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/direct_response create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/envoy_patch_policy create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/extension_server create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/external_auth create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/failover create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/fault_injection create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/global_rate_limit rename test/fuzz/testdata/FuzzGatewayAPIToXDS/{gateway_and_grpcroute => grpc_routing} (100%) delete mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/grpcroute create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/http_redirect create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/http_request_header create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/http_response_header rename test/fuzz/testdata/FuzzGatewayAPIToXDS/{gateway_and_httproute => http_routing} (100%) create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/http_timeout create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/http_traffic_splitting create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/http_url_rewrite delete mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/httproute create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/ip_allowlist create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/jwt_auth create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/jwt_claim_based_auth create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/load_balancing create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/local_ratelimit create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/mtls_client_to_gateway create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/mtls_gateway_to_backend create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/multi_tenancy create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/oidc_auth create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/proxy_metrics create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/proxy_tracing create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/response_override create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/retry create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/secure_gateway create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/session_persistence rename test/fuzz/testdata/FuzzGatewayAPIToXDS/{gateway_and_tcproute => tcp_routing} (100%) delete mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/tcproute create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/tls_gateway_to_backend rename test/fuzz/testdata/FuzzGatewayAPIToXDS/{gateway_and_udproute => udp_routing} (100%) delete mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/udproute create mode 100644 test/fuzz/testdata/FuzzGatewayAPIToXDS/wasm_extension diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/api_key_auth b/test/fuzz/testdata/FuzzGatewayAPIToXDS/api_key_auth new file mode 100644 index 0000000000..b7622cf032 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/api_key_auth @@ -0,0 +1,25 @@ +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: apikey-secret +stringData: + client1: supersecret +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: apikey-auth-example +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: backend + apiKeyAuth: + credentialRefs: + - group: "" + kind: Secret + name: apikey-secret + extractFrom: + - headers: + - x-api-key \ No newline at end of file diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/backend_routing b/test/fuzz/testdata/FuzzGatewayAPIToXDS/backend_routing new file mode 100644 index 0000000000..3659926021 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/backend_routing @@ -0,0 +1,46 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: envoy-gateway-config + namespace: envoy-gateway-system +data: + envoy-gateway.yaml: | + apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyGateway + provider: + type: Kubernetes + gateway: + controllerName: gateway.envoyproxy.io/gatewayclass-controller + extensionApis: + enableBackend: true +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: backend +spec: + parentRefs: + - name: eg + hostnames: + - "www.example.com" + rules: + - backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: httpbin + matches: + - path: + type: PathPrefix + value: / +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: Backend +metadata: + name: httpbin + namespace: default +spec: + endpoints: + - fqdn: + hostname: httpbin.org + port: 80 \ No newline at end of file diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/basic_auth b/test/fuzz/testdata/FuzzGatewayAPIToXDS/basic_auth new file mode 100644 index 0000000000..51d692a4b5 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/basic_auth @@ -0,0 +1,13 @@ +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: basic-auth-example +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: backend + basicAuth: + users: + name: "basic-auth" diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/circuit_breaker b/test/fuzz/testdata/FuzzGatewayAPIToXDS/circuit_breaker new file mode 100644 index 0000000000..bc1768662d --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/circuit_breaker @@ -0,0 +1,13 @@ +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: circuitbreaker-for-route +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: backend + circuitBreaker: + maxPendingRequests: 0 + maxParallelRequests: 10 \ No newline at end of file diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/client_traffic_policy b/test/fuzz/testdata/FuzzGatewayAPIToXDS/client_traffic_policy new file mode 100644 index 0000000000..5e861ddbc9 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/client_traffic_policy @@ -0,0 +1,84 @@ +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: ClientTrafficPolicy +metadata: + name: enable-tcp-keepalive-policy + namespace: default +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: eg + tcpKeepalive: + idleTime: 20m + interval: 60s + probes: 3 + +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: ClientTrafficPolicy +metadata: + name: enable-proxy-protocol-policy + namespace: default +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: eg + enableProxyProtocol: true + +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: ClientTrafficPolicy +metadata: + name: http-client-ip-detection + namespace: default +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: eg + clientIPDetection: + xForwardedFor: + numTrustedHops: 2 + +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: ClientTrafficPolicy +metadata: + name: client-timeout +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: eg + timeout: + http: + requestReceivedTimeout: 2s + +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: ClientTrafficPolicy +metadata: + name: client-timeout +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: eg + timeout: + http: + idleTimeout: 5s + +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: ClientTrafficPolicy +metadata: + name: client-timeout +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: eg + connection: + bufferLimit: 1024 diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/configmap b/test/fuzz/testdata/FuzzGatewayAPIToXDS/configmap deleted file mode 100644 index e19bde2ccb..0000000000 --- a/test/fuzz/testdata/FuzzGatewayAPIToXDS/configmap +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: configmap - namespace: default -data: - player_initial_lives: "3" - game.properties: | - enemy.types=aliens,monsters - player.maximum-lives=5 diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/connection_limit b/test/fuzz/testdata/FuzzGatewayAPIToXDS/connection_limit new file mode 100644 index 0000000000..27d0458864 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/connection_limit @@ -0,0 +1,14 @@ +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: ClientTrafficPolicy +metadata: + name: connection-limit-ctp + namespace: default +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: eg + connection: + connectionLimit: + value: 5 diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/cors b/test/fuzz/testdata/FuzzGatewayAPIToXDS/cors new file mode 100644 index 0000000000..09c0b94ae0 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/cors @@ -0,0 +1,23 @@ +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: cors-example +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: backend + cors: + allowOrigins: + - "http://*.foo.com" + - "http://*.foo.com:80" + allowMethods: + - GET + - POST + allowHeaders: + - "x-header-1" + - "x-header-2" + exposeHeaders: + - "x-header-3" + - "x-header-4" diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/direct_response b/test/fuzz/testdata/FuzzGatewayAPIToXDS/direct_response new file mode 100644 index 0000000000..3f7370a85e --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/direct_response @@ -0,0 +1,65 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: direct-response +spec: + parentRefs: + - name: eg + hostnames: + - "www.example.com" + rules: + - matches: + - path: + type: PathPrefix + value: /inline + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: direct-response-inline + - matches: + - path: + type: PathPrefix + value: /value-ref + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: direct-response-value-ref +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: value-ref-response +data: + response.body: '{"error": "Internal Server Error"}' +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: HTTPRouteFilter +metadata: + name: direct-response-inline +spec: + directResponse: + contentType: text/plain + statusCode: 503 + body: + type: Inline + inline: "Oops! Your request is not found." +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: HTTPRouteFilter +metadata: + name: direct-response-value-ref +spec: + directResponse: + contentType: application/json + statusCode: 500 + body: + type: ValueRef + valueRef: + group: "" + kind: ConfigMap + name: value-ref-response diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/envoy_patch_policy b/test/fuzz/testdata/FuzzGatewayAPIToXDS/envoy_patch_policy new file mode 100644 index 0000000000..5210a33fcc --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/envoy_patch_policy @@ -0,0 +1,31 @@ +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: EnvoyPatchPolicy +metadata: + name: custom-response-patch-policy + namespace: default +spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: eg + type: JSONPatch + jsonPatches: + - type: "type.googleapis.com/envoy.config.listener.v3.Listener" + # The listener name is of the form // + name: default/eg/http + operation: + op: add + path: "/default_filter_chain/filters/0/typed_config/local_reply_config" + value: + mappers: + - filter: + status_code_filter: + comparison: + op: EQ + value: + default_value: 404 + runtime_key: key_b + status_code: 406 + body: + inline_string: "could not find what you are looking for" diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/extension_server b/test/fuzz/testdata/FuzzGatewayAPIToXDS/extension_server new file mode 100644 index 0000000000..05a95e2187 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/extension_server @@ -0,0 +1,54 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: myapp +spec: + parentRefs: + - name: eg + hostnames: + - "www.example.com" + rules: + - matches: + - path: + type: PathPrefix + value: /myapp + backendRefs: + - name: backend + port: 3000 + +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: EnvoyExtensionPolicy +metadata: + name: ext-proc-example +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: myapp + extProc: + - backendRefs: + - name: grpc-ext-proc + port: 9002 + processingMode: + request: {} + response: + body: Streamed + +--- +apiVersion: gateway.networking.k8s.io/v1alpha3 +kind: BackendTLSPolicy +metadata: + name: grpc-ext-proc-btls +spec: + targetRefs: + - group: '' + kind: Service + name: grpc-ext-proc + validation: + caCertificateRefs: + - name: grpc-ext-proc-ca + group: '' + kind: ConfigMap + hostname: grpc-ext-proc.envoygateway diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/external_auth b/test/fuzz/testdata/FuzzGatewayAPIToXDS/external_auth new file mode 100644 index 0000000000..2ec8f89d2b --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/external_auth @@ -0,0 +1,34 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: myapp +spec: + parentRefs: + - name: eg + hostnames: + - "www.example.com" + rules: + - matches: + - path: + type: PathPrefix + value: /myapp + backendRefs: + - name: backend + port: 3000 +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: ext-auth-example +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: myapp + extAuth: + http: + backendRefs: + - name: http-ext-auth + port: 9002 + headersToBackend: ["x-current-user"] diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/failover b/test/fuzz/testdata/FuzzGatewayAPIToXDS/failover new file mode 100644 index 0000000000..cb5882064c --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/failover @@ -0,0 +1,201 @@ +apiVersion: v1 +kind: Service +metadata: + name: active + labels: + app: active + service: active +spec: + ports: + - name: http + port: 3000 + targetPort: 3000 + selector: + app: active +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: active +spec: + replicas: 1 + selector: + matchLabels: + app: active + version: v1 + template: + metadata: + labels: + app: active + version: v1 + spec: + containers: + - image: gcr.io/k8s-staging-gateway-api/echo-basic:v20231214-v1.0.0-140-gf544a46e + imagePullPolicy: IfNotPresent + name: active + ports: + - containerPort: 3000 + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace +--- +apiVersion: v1 +kind: Service +metadata: + name: passive + labels: + app: passive + service: passive +spec: + ports: + - name: http + port: 3000 + targetPort: 3000 + selector: + app: passive +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: passive +spec: + replicas: 1 + selector: + matchLabels: + app: passive + version: v1 + template: + metadata: + labels: + app: passive + version: v1 + spec: + containers: + - image: gcr.io/k8s-staging-gateway-api/echo-basic:v20231214-v1.0.0-140-gf544a46e + imagePullPolicy: IfNotPresent + name: passive + ports: + - containerPort: 3000 + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: Backend +metadata: + name: passive +spec: + fallback: true + endpoints: + - fqdn: + hostname: passive.default.svc.cluster.local + port: 3000 +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: Backend +metadata: + name: active +spec: + endpoints: + - fqdn: + hostname: active.default.svc.cluster.local + port: 3000 +--- + +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: ha-example + namespace: default +spec: + hostnames: + - www.example.com + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: eg + namespace: default + rules: + - backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: active + namespace: default + port: 3000 + - group: gateway.envoyproxy.io + kind: Backend + name: passive + namespace: default + port: 3000 + matches: + - path: + type: PathPrefix + value: /test + +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: passive-health-check +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: ha-example + healthCheck: + passive: + baseEjectionTime: 10s + interval: 2s + maxEjectionPercent: 100 + consecutive5XxErrors: 1 + consecutiveGatewayErrors: 0 + consecutiveLocalOriginFailures: 1 + splitExternalLocalOriginErrors: false + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: active +spec: + replicas: 1 + selector: + matchLabels: + app: active + version: v1 + template: + metadata: + labels: + app: active + version: v1 + spec: + containers: + - image: gcr.io/k8s-staging-gateway-api/echo-basic:v20231214-v1.0.0-140-gf544a46e + imagePullPolicy: IfNotPresent + name: active + ports: + - containerPort: 3000 + env: + - name: HTTP_PORT + value: "5000" + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/fault_injection b/test/fuzz/testdata/FuzzGatewayAPIToXDS/fault_injection new file mode 100644 index 0000000000..8691a60c01 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/fault_injection @@ -0,0 +1,69 @@ +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: fault-injection-50-percent-abort +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: foo + faultInjection: + abort: + httpStatus: 501 + percentage: 50 +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: fault-injection-delay +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: bar + faultInjection: + delay: + fixedDelay: 2s +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: foo +spec: + parentRefs: + - name: eg + hostnames: + - "www.example.com" + rules: + - backendRefs: + - group: "" + kind: Service + name: backend + port: 3000 + weight: 1 + matches: + - path: + type: PathPrefix + value: /foo +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: bar +spec: + parentRefs: + - name: eg + hostnames: + - "www.example.com" + rules: + - backendRefs: + - group: "" + kind: Service + name: backend + port: 3000 + weight: 1 + matches: + - path: + type: PathPrefix + value: /bar diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/global_rate_limit b/test/fuzz/testdata/FuzzGatewayAPIToXDS/global_rate_limit new file mode 100644 index 0000000000..d4f91e5cd3 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/global_rate_limit @@ -0,0 +1,71 @@ +--- +kind: Namespace +apiVersion: v1 +metadata: + name: redis-system +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + namespace: redis-system + labels: + app: redis +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + containers: + - image: redis:6.0.6 + imagePullPolicy: IfNotPresent + name: redis + resources: + limits: + cpu: 1500m + memory: 512Mi + requests: + cpu: 200m + memory: 256Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: redis + namespace: redis-system + labels: + app: redis + annotations: +spec: + ports: + - name: redis + port: 6379 + protocol: TCP + targetPort: 6379 + selector: + app: redis + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: envoy-gateway-config + namespace: envoy-gateway-system +data: + envoy-gateway.yaml: | + apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyGateway + provider: + type: Kubernetes + gateway: + controllerName: gateway.envoyproxy.io/gatewayclass-controller + rateLimit: + backend: + type: Redis + redis: + url: redis.redis-system.svc.cluster.local:6379 diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway_and_grpcroute b/test/fuzz/testdata/FuzzGatewayAPIToXDS/grpc_routing similarity index 100% rename from test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway_and_grpcroute rename to test/fuzz/testdata/FuzzGatewayAPIToXDS/grpc_routing diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/grpcroute b/test/fuzz/testdata/FuzzGatewayAPIToXDS/grpcroute deleted file mode 100644 index dfb260f20f..0000000000 --- a/test/fuzz/testdata/FuzzGatewayAPIToXDS/grpcroute +++ /dev/null @@ -1,22 +0,0 @@ -apiVersion: gateway.networking.k8s.io/v1 -kind: GRPCRoute -metadata: - name: backend - namespace: default -spec: - parentRefs: - - name: eg - sectionName: grpc - hostnames: - - "www.grpc-example.com" - rules: - - matches: - - method: - service: com.example.Things - method: DoThing - headers: - - name: com.example.Header - value: foobar - backendRefs: - - name: providedBackend - port: 9000 \ No newline at end of file diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/http_redirect b/test/fuzz/testdata/FuzzGatewayAPIToXDS/http_redirect new file mode 100644 index 0000000000..eeffdfb6fb --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/http_redirect @@ -0,0 +1,99 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: http-to-https-filter-redirect +spec: + parentRefs: + - name: eg + hostnames: + - redirect.example + rules: + - filters: + - type: RequestRedirect + requestRedirect: + scheme: https + statusCode: 301 + hostname: www.example.com +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: eg +spec: + gatewayClassName: eg + listeners: + - name: http + port: 80 + protocol: HTTP + # hostname: "*.example.com" + - name: https + port: 443 + protocol: HTTPS + # hostname: "*.example.com" + tls: + mode: Terminate + certificateRefs: + - kind: Secret + name: example-com +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: tls-redirect +spec: + parentRefs: + - name: eg + sectionName: http + hostnames: + # - "*.example.com" # catch all hostnames + - "www.example.com" + rules: + - filters: + - type: RequestRedirect + requestRedirect: + scheme: https +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: backend +spec: + parentRefs: + - name: eg + sectionName: https + hostnames: + - "www.example.com" + rules: + - backendRefs: + - group: "" + kind: Service + name: backend + port: 3000 + weight: 1 + matches: + - path: + type: PathPrefix + value: / +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: http-filter-path-redirect +spec: + parentRefs: + - name: eg + hostnames: + - path.redirect.example + rules: + - matches: + - path: + type: PathPrefix + value: /get + filters: + - type: RequestRedirect + requestRedirect: + path: + type: ReplaceFullPath + replaceFullPath: /status/200 + statusCode: 302 diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/http_request_header b/test/fuzz/testdata/FuzzGatewayAPIToXDS/http_request_header new file mode 100644 index 0000000000..f4144da1dc --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/http_request_header @@ -0,0 +1,52 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: http-headers +spec: + parentRefs: + - name: eg + hostnames: + - headers.example + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - group: "" + kind: Service + name: backend + port: 3000 + weight: 1 + filters: + - type: RequestHeaderModifier + requestHeaderModifier: + add: + - name: early-added-header + value: late + - name: early-set-header + value: late + - name: early-removed-header + value: late +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: ClientTrafficPolicy +metadata: + name: enable-early-headers + namespace: default +spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: eg + headers: + earlyRequestHeaders: + add: + - name: "early-added-header" + value: "early" + set: + - name: "early-set-header" + value: "early" + remove: + - "early-removed-header" diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/http_response_header b/test/fuzz/testdata/FuzzGatewayAPIToXDS/http_response_header new file mode 100644 index 0000000000..dfe93b95ce --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/http_response_header @@ -0,0 +1,32 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: http-headers +spec: + parentRefs: + - name: eg + hostnames: + - headers.example + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - group: "" + kind: Service + name: backend + port: 3000 + weight: 1 + filters: + - type: ResponseHeaderModifier + responseHeaderModifier: + add: + - name: "add-header-1" + value: "foo" + set: + - name: "set-header-1" + value: "bar" + remove: + - "removed-header" diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway_and_httproute b/test/fuzz/testdata/FuzzGatewayAPIToXDS/http_routing similarity index 100% rename from test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway_and_httproute rename to test/fuzz/testdata/FuzzGatewayAPIToXDS/http_routing diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/http_timeout b/test/fuzz/testdata/FuzzGatewayAPIToXDS/http_timeout new file mode 100644 index 0000000000..8c1d028602 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/http_timeout @@ -0,0 +1,25 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: backend +spec: + hostnames: + - timeout.example.com + parentRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: eg + rules: + - backendRefs: + - group: "" + kind: Service + name: backend + port: 3000 + weight: 1 + matches: + - path: + type: PathPrefix + value: / + timeouts: + request: "2s" diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/http_traffic_splitting b/test/fuzz/testdata/FuzzGatewayAPIToXDS/http_traffic_splitting new file mode 100644 index 0000000000..ba6c4c4a36 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/http_traffic_splitting @@ -0,0 +1,53 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: backend-2 +--- +apiVersion: v1 +kind: Service +metadata: + name: backend-2 + labels: + app: backend-2 + service: backend-2 +spec: + ports: + - name: http + port: 3000 + targetPort: 3000 + selector: + app: backend-2 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: backend-2 +spec: + replicas: 1 + selector: + matchLabels: + app: backend-2 + version: v1 + template: + metadata: + labels: + app: backend-2 + version: v1 + spec: + serviceAccountName: backend-2 + containers: + - image: gcr.io/k8s-staging-gateway-api/echo-basic:v20231214-v1.0.0-140-gf544a46e + imagePullPolicy: IfNotPresent + name: backend-2 + ports: + - containerPort: 3000 + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/http_url_rewrite b/test/fuzz/testdata/FuzzGatewayAPIToXDS/http_url_rewrite new file mode 100644 index 0000000000..2005eb7529 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/http_url_rewrite @@ -0,0 +1,36 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: http-filter-url-regex-rewrite +spec: + parentRefs: + - name: eg + hostnames: + - path.regex.rewrite.example + rules: + - matches: + - path: + type: PathPrefix + value: "/" + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: regex-path-rewrite + backendRefs: + - name: backend + port: 3000 +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: HTTPRouteFilter +metadata: + name: regex-path-rewrite +spec: + urlRewrite: + path: + type: ReplaceRegexMatch + replaceRegexMatch: + pattern: '^/service/([^/]+)(/.*)$' + substitution: '\2/instance/\1' diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/httproute b/test/fuzz/testdata/FuzzGatewayAPIToXDS/httproute deleted file mode 100644 index 5fd38114fb..0000000000 --- a/test/fuzz/testdata/FuzzGatewayAPIToXDS/httproute +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: gateway.networking.k8s.io/v1 -kind: HTTPRoute -metadata: - name: backend - namespace: default -spec: - parentRefs: - - name: eg - hostnames: - - "www.example.com" - rules: - - backendRefs: - - name: providedBackend - port: 8000 \ No newline at end of file diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/ip_allowlist b/test/fuzz/testdata/FuzzGatewayAPIToXDS/ip_allowlist new file mode 100644 index 0000000000..0e46a848d7 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/ip_allowlist @@ -0,0 +1,30 @@ +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: authorization-client-ip +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: backend + authorization: + defaultAction: Deny + rules: + - action: Allow + principal: + clientCIDRs: + - 10.0.1.0/24 +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: ClientTrafficPolicy +metadata: + name: enable-client-ip-detection +spec: + clientIPDetection: + xForwardedFor: + numTrustedHops: 1 + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: eg diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/jwt_auth b/test/fuzz/testdata/FuzzGatewayAPIToXDS/jwt_auth new file mode 100644 index 0000000000..df3f1030e0 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/jwt_auth @@ -0,0 +1,56 @@ +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: jwt-example +spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: foo + jwt: + providers: + - name: example + remoteJWKS: + backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: remote-jwks + port: 443 + backendSettings: + retry: + numRetries: 3 + perRetry: + backOff: + baseInterval: 1s + maxInterval: 5s + retryOn: + triggers: ["5xx", "gateway-error", "reset"] + uri: https://foo.bar.com/jwks.json +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: Backend +metadata: + name: remote-jwks +spec: + endpoints: + - fqdn: + hostname: foo.bar.com + port: 443 +--- +apiVersion: gateway.networking.k8s.io/v1alpha3 +kind: BackendTLSPolicy +metadata: + name: remote-jwks-btls +spec: + targetRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: remote-jwks + sectionName: "443" + validation: + caCertificateRefs: + - name: remote-jwks-server-ca + group: "" + kind: ConfigMap + hostname: foo.bar.com diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/jwt_claim_based_auth b/test/fuzz/testdata/FuzzGatewayAPIToXDS/jwt_claim_based_auth new file mode 100644 index 0000000000..deb74cd4c4 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/jwt_claim_based_auth @@ -0,0 +1,31 @@ +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: authorization-jwt-claim +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: backend + jwt: + providers: + - name: example + issuer: https://foo.bar.com + remoteJWKS: + uri: https://raw.githubusercontent.com/envoyproxy/gateway/refs/heads/main/examples/kubernetes/jwt/jwks.json + authorization: + defaultAction: Deny + rules: + - name: "allow" + action: Allow + principal: + jwt: + provider: example + scopes: ["read", "add", "modify"] + claims: + - name: user.name + values: ["John Doe"] + - name: user.roles + valueType: StringArray + values: ["admin"] diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/load_balancing b/test/fuzz/testdata/FuzzGatewayAPIToXDS/load_balancing new file mode 100644 index 0000000000..033edcb5fc --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/load_balancing @@ -0,0 +1,32 @@ +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: round-robin-policy + namespace: default +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: round-robin-route + loadBalancer: + type: RoundRobin +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: round-robin-route + namespace: default +spec: + parentRefs: + - name: eg + hostnames: + - "www.example.com" + rules: + - matches: + - path: + type: PathPrefix + value: /round + backendRefs: + - name: backend + port: 3000 diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/local_ratelimit b/test/fuzz/testdata/FuzzGatewayAPIToXDS/local_ratelimit new file mode 100644 index 0000000000..cb310fa986 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/local_ratelimit @@ -0,0 +1,41 @@ +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: policy-httproute +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: http-ratelimit + rateLimit: + type: Local + local: + rules: + - clientSelectors: + - headers: + - name: x-user-id + value: one + limit: + requests: 3 + unit: Hour +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: http-ratelimit +spec: + parentRefs: + - name: eg + hostnames: + - ratelimit.example + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - group: "" + kind: Service + name: backend + port: 3000 diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/mtls_client_to_gateway b/test/fuzz/testdata/FuzzGatewayAPIToXDS/mtls_client_to_gateway new file mode 100644 index 0000000000..0f223a7d34 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/mtls_client_to_gateway @@ -0,0 +1,17 @@ +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: ClientTrafficPolicy +metadata: + name: enable-mtls + namespace: default +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: eg + tls: + clientValidation: + caCertificateRefs: + - kind: "Secret" + group: "" + name: "example-ca-cert" diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/mtls_gateway_to_backend b/test/fuzz/testdata/FuzzGatewayAPIToXDS/mtls_gateway_to_backend new file mode 100644 index 0000000000..38463cc757 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/mtls_gateway_to_backend @@ -0,0 +1,24 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: GatewayClass +metadata: + name: eg +spec: + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parametersRef: + group: gateway.envoyproxy.io + kind: EnvoyProxy + name: custom-proxy-config + namespace: envoy-gateway-system +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: EnvoyProxy +metadata: + name: custom-proxy-config + namespace: envoy-gateway-system +spec: + backendTLS: + clientCertificateRef: + kind: Secret + name: example-client-cert + namespace: envoy-gateway-system diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/multi_tenancy b/test/fuzz/testdata/FuzzGatewayAPIToXDS/multi_tenancy new file mode 100644 index 0000000000..acef74011c --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/multi_tenancy @@ -0,0 +1,97 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: GatewayClass +metadata: + name: eg-marketing +spec: + controllerName: gateway.envoyproxy.io/marketing-gatewayclass-controller +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: eg + namespace: marketing +spec: + gatewayClassName: eg-marketing + listeners: + - name: http + protocol: HTTP + port: 8080 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: backend + namespace: marketing +--- +apiVersion: v1 +kind: Service +metadata: + name: backend + namespace: marketing + labels: + app: backend + service: backend +spec: + ports: + - name: http + port: 3000 + targetPort: 3000 + selector: + app: backend +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: backend + namespace: marketing +spec: + replicas: 1 + selector: + matchLabels: + app: backend + version: v1 + template: + metadata: + labels: + app: backend + version: v1 + spec: + serviceAccountName: backend + containers: + - image: gcr.io/k8s-staging-gateway-api/echo-basic:v20231214-v1.0.0-140-gf544a46e + imagePullPolicy: IfNotPresent + name: backend + ports: + - containerPort: 3000 + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: backend + namespace: marketing +spec: + parentRefs: + - name: eg + hostnames: + - "www.marketing.example.com" + rules: + - backendRefs: + - group: "" + kind: Service + name: backend + port: 3000 + weight: 1 + matches: + - path: + type: PathPrefix + value: / diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/oidc_auth b/test/fuzz/testdata/FuzzGatewayAPIToXDS/oidc_auth new file mode 100644 index 0000000000..8d48cad0e4 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/oidc_auth @@ -0,0 +1,17 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: myapp +spec: + parentRefs: + - name: eg + hostnames: ["www.example.com"] + rules: + - matches: + - path: + type: PathPrefix + value: /myapp + backendRefs: + - name: backend + port: 3000 diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/proxy_metrics b/test/fuzz/testdata/FuzzGatewayAPIToXDS/proxy_metrics new file mode 100644 index 0000000000..8700ff9624 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/proxy_metrics @@ -0,0 +1,27 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: eg + namespace: envoy-gateway-system +spec: + gatewayClassName: eg + infrastructure: + parametersRef: + group: gateway.envoyproxy.io + kind: EnvoyProxy + name: prometheus + listeners: + - name: http + protocol: HTTP + port: 80 +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: EnvoyProxy +metadata: + name: prometheus + namespace: envoy-gateway-system +spec: + telemetry: + metrics: + prometheus: + disable: true diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/proxy_tracing b/test/fuzz/testdata/FuzzGatewayAPIToXDS/proxy_tracing new file mode 100644 index 0000000000..b9c8baa433 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/proxy_tracing @@ -0,0 +1,50 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: GatewayClass +metadata: + name: eg +spec: + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parametersRef: + group: gateway.envoyproxy.io + kind: EnvoyProxy + name: otel + namespace: envoy-gateway-system +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: EnvoyProxy +metadata: + name: otel + namespace: envoy-gateway-system +spec: + telemetry: + tracing: + # sample 1% of requests + samplingRate: 1 + provider: + backendRefs: + - name: otel-collector + namespace: monitoring + port: 4317 + type: OpenTelemetry + customTags: + # This is an example of using a literal as a tag value + provider: + type: Literal + literal: + value: "otel" + "k8s.pod.name": + type: Environment + environment: + name: ENVOY_POD_NAME + defaultValue: "-" + "k8s.namespace.name": + type: Environment + environment: + name: ENVOY_GATEWAY_NAMESPACE + defaultValue: "envoy-gateway-system" + # This is an example of using a header value as a tag value + header1: + type: RequestHeader + requestHeader: + name: X-Header-1 + defaultValue: "-" \ No newline at end of file diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/response_override b/test/fuzz/testdata/FuzzGatewayAPIToXDS/response_override new file mode 100644 index 0000000000..6727e23eb5 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/response_override @@ -0,0 +1,43 @@ +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: response-override +spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: backend + responseOverride: + - match: + statusCodes: + - type: Value + value: 404 + response: + contentType: text/plain + body: + type: Inline + inline: "Oops! Your request is not found." + - match: + statusCodes: + - type: Value + value: 500 + - type: Range + range: + start: 501 + end: 511 + response: + contentType: application/json + body: + type: ValueRef + valueRef: + group: "" + kind: ConfigMap + name: response-override-config +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: response-override-config +data: + response.body: '{"error": "Internal Server Error"}' diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/retry b/test/fuzz/testdata/FuzzGatewayAPIToXDS/retry new file mode 100644 index 0000000000..b5e1cbfa0e --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/retry @@ -0,0 +1,23 @@ +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: retry-for-route +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: backend + retry: + numRetries: 5 + perRetry: + backOff: + baseInterval: 100ms + maxInterval: 10s + timeout: 250ms + retryOn: + httpStatusCodes: + - 500 + triggers: + - connect-failure + - retriable-status-codes diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/secure_gateway b/test/fuzz/testdata/FuzzGatewayAPIToXDS/secure_gateway new file mode 100644 index 0000000000..8d48cad0e4 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/secure_gateway @@ -0,0 +1,17 @@ +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: myapp +spec: + parentRefs: + - name: eg + hostnames: ["www.example.com"] + rules: + - matches: + - path: + type: PathPrefix + value: /myapp + backendRefs: + - name: backend + port: 3000 diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/session_persistence b/test/fuzz/testdata/FuzzGatewayAPIToXDS/session_persistence new file mode 100644 index 0000000000..3da6f06762 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/session_persistence @@ -0,0 +1,42 @@ +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: cookie +spec: + parentRefs: + - name: eg + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: backend + port: 3000 + sessionPersistence: + sessionName: Session-A + type: Cookie + absoluteTimeout: 10s + cookieConfig: + lifetimeType: Permanent +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: header +spec: + parentRefs: + - name: eg + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: backend + port: 3000 + sessionPersistence: + sessionName: Session-A + type: Header + absoluteTimeout: 10s diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway_and_tcproute b/test/fuzz/testdata/FuzzGatewayAPIToXDS/tcp_routing similarity index 100% rename from test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway_and_tcproute rename to test/fuzz/testdata/FuzzGatewayAPIToXDS/tcp_routing diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/tcproute b/test/fuzz/testdata/FuzzGatewayAPIToXDS/tcproute deleted file mode 100644 index 1ba7b45909..0000000000 --- a/test/fuzz/testdata/FuzzGatewayAPIToXDS/tcproute +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: gateway.networking.k8s.io/v1alpha2 -kind: TCPRoute -metadata: - name: backend - namespace: default -spec: - parentRefs: - - name: eg - sectionName: tcp - rules: - - backendRefs: - - name: backend - port: 3000 \ No newline at end of file diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/tls_gateway_to_backend b/test/fuzz/testdata/FuzzGatewayAPIToXDS/tls_gateway_to_backend new file mode 100644 index 0000000000..e1e0056121 --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/tls_gateway_to_backend @@ -0,0 +1,35 @@ +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: backend + service: backend + name: tls-backend + namespace: default +spec: + selector: + app: backend + ports: + - name: https + port: 443 + protocol: TCP + targetPort: 8443 +--- +apiVersion: gateway.networking.k8s.io/v1alpha3 +kind: BackendTLSPolicy +metadata: + name: enable-backend-tls + namespace: default +spec: + targetRefs: + - group: '' + kind: Service + name: tls-backend + sectionName: https + validation: + caCertificateRefs: + - name: example-ca + group: '' + kind: ConfigMap + hostname: www.example.com diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway_and_udproute b/test/fuzz/testdata/FuzzGatewayAPIToXDS/udp_routing similarity index 100% rename from test/fuzz/testdata/FuzzGatewayAPIToXDS/gateway_and_udproute rename to test/fuzz/testdata/FuzzGatewayAPIToXDS/udp_routing diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/udproute b/test/fuzz/testdata/FuzzGatewayAPIToXDS/udproute deleted file mode 100644 index 9dea136e87..0000000000 --- a/test/fuzz/testdata/FuzzGatewayAPIToXDS/udproute +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: gateway.networking.k8s.io/v1alpha2 -kind: UDPRoute -metadata: - name: backend - namespace: default -spec: - parentRefs: - - name: eg - sectionName: udp - rules: - - backendRefs: - - name: backend - port: 3000 \ No newline at end of file diff --git a/test/fuzz/testdata/FuzzGatewayAPIToXDS/wasm_extension b/test/fuzz/testdata/FuzzGatewayAPIToXDS/wasm_extension new file mode 100644 index 0000000000..49e588fd4e --- /dev/null +++ b/test/fuzz/testdata/FuzzGatewayAPIToXDS/wasm_extension @@ -0,0 +1,18 @@ +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: EnvoyExtensionPolicy +metadata: + name: wasm-test +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: backend + wasm: + - name: wasm-filter + rootID: my_root_id + code: + type: HTTP + http: + url: https://raw.githubusercontent.com/envoyproxy/examples/main/wasm-cc/lib/envoy_filter_http_wasm_example.wasm + sha256: 79c9f85128bb0177b6511afa85d587224efded376ac0ef76df56595f1e6315c0 From 565ea8ad127e86882a89c83093b79edbff4bbc6b Mon Sep 17 00:00:00 2001 From: Guy Daich Date: Thu, 8 May 2025 18:28:20 -0500 Subject: [PATCH 35/66] [release/v1.3] release v1.3.3 notes (#5969) release v1.3.3 notes Signed-off-by: Guy Daich Signed-off-by: Arko Dasgupta --- release-notes/v1.3.3.yaml | 14 ++++++++++++++ site/content/en/news/releases/notes/v1.3.3.md | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 release-notes/v1.3.3.yaml create mode 100644 site/content/en/news/releases/notes/v1.3.3.md diff --git a/release-notes/v1.3.3.yaml b/release-notes/v1.3.3.yaml new file mode 100644 index 0000000000..9b45e4f5eb --- /dev/null +++ b/release-notes/v1.3.3.yaml @@ -0,0 +1,14 @@ +date: May 8, 2025 + +bug fixes: | + Fix issue where ReferenceGrant from SecurityPolicy to the referenced RemoteJWKS backend was not respected. + Fix HTTPRoute precedence by correctly considering header and query match types. + Fix to return an error if direct response size exceeds the limit. + Fix to avoid adding the TLS inspector filter to QUIC listeners. + Fix to continue processing remaining GatewayClasses after encountering an error. + Add validation for header values. + +# Other notable changes not covered by the above sections. +Other changes: | + Bumped envoy to v1.33.3. + Bumped ratelimit to 3e085e5b. diff --git a/site/content/en/news/releases/notes/v1.3.3.md b/site/content/en/news/releases/notes/v1.3.3.md new file mode 100644 index 0000000000..1f8b83fc37 --- /dev/null +++ b/site/content/en/news/releases/notes/v1.3.3.md @@ -0,0 +1,19 @@ +--- +title: "v1.3.3" +publishdate: 2025-05-08 +--- + +Date: May 8, 2025 + +## Bug fixes +- Fix issue where ReferenceGrant from SecurityPolicy to the referenced RemoteJWKS backend was not respected. +- Fix HTTPRoute precedence by correctly considering header and query match types. +- Fix to return an error if direct response size exceeds the limit. +- Fix to avoid adding the TLS inspector filter to QUIC listeners. +- Fix to continue processing remaining GatewayClasses after encountering an error. +- Add validation for header values. + +## Other changes +- Bumped envoy to v1.33.3. +- Bumped ratelimit to 3e085e5b. + From d6bc7d469f252520bdcdd7c9b5ba0d4b690767aa Mon Sep 17 00:00:00 2001 From: zirain Date: Fri, 9 May 2025 08:51:21 +0800 Subject: [PATCH 36/66] e2e: fix PreserveCase flaky (#5966) * e2e: fix PreserveCase flaky Signed-off-by: zirain * fix Signed-off-by: zirain --------- Signed-off-by: zirain Signed-off-by: Arko Dasgupta --- test/e2e/tests/preservecase.go | 51 +++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/test/e2e/tests/preservecase.go b/test/e2e/tests/preservecase.go index 1754186f35..41d731168b 100644 --- a/test/e2e/tests/preservecase.go +++ b/test/e2e/tests/preservecase.go @@ -16,13 +16,16 @@ import ( "net/http/httputil" "regexp" "testing" + "time" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" "sigs.k8s.io/gateway-api/conformance/utils/http" "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" "sigs.k8s.io/gateway-api/conformance/utils/roundtripper" "sigs.k8s.io/gateway-api/conformance/utils/suite" + "sigs.k8s.io/gateway-api/conformance/utils/tlog" ) func init() { @@ -113,27 +116,37 @@ var PreserveCaseTest = suite.ConformanceTest{ gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) WaitForPods(t, suite.Client, "gateway-preserve-case-backend", map[string]string{"app": "preserve-case"}, corev1.PodRunning, PodReady) - // Can't use the standard method for checking the response, since the remote side isn't the - // conformance echo server and it returns a differently formatted response. - expectedResponse := http.ExpectedResponse{ - Request: http.Request{ - Path: "/preserve?headers=ReSpOnSeHeAdEr", - Headers: map[string]string{ - "SpEcIaL": "Header", - }, - }, - Namespace: ns, - } - var rt nethttp.RoundTripper - req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http") - respBody, err := casePreservingRoundTrip(req, rt, suite) + err := wait.PollUntilContextTimeout(context.TODO(), time.Second, suite.TimeoutConfig.DeleteTimeout, true, func(ctx context.Context) (bool, error) { + // Can't use the standard method for checking the response, since the remote side isn't the + // conformance echo server and it returns a differently formatted response. + expectedResponse := http.ExpectedResponse{ + Request: http.Request{ + Path: "/preserve?headers=ReSpOnSeHeAdEr", + Headers: map[string]string{ + "SpEcIaL": "Header", + }, + }, + Namespace: ns, + } + + var rt nethttp.RoundTripper + req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http") + respBody, err := casePreservingRoundTrip(req, rt, suite) + if err != nil { + tlog.Logf(t, "failed to get expected response: %v", err) + return false, nil + } + + if _, found := respBody["SpEcIaL"]; !found { + tlog.Logf(t, "case was not preserved for test header: %+v", respBody) + return false, nil + } + + return true, nil + }) if err != nil { - t.Errorf("failed to get expected response: %v", err) - } - - if _, found := respBody["SpEcIaL"]; !found { - t.Errorf("case was not preserved for test header: %+v", respBody) + tlog.Errorf(t, "failed to get expected response: %v", err) } }) }, From 2fc0c536247bef0b2351d09ea8eb09cc2da62ea5 Mon Sep 17 00:00:00 2001 From: Karol Szwaj Date: Fri, 9 May 2025 10:32:23 +0200 Subject: [PATCH 37/66] feat: validate JWT token and use projected token (#5871) * Add proxyMetadata to xds config and validate JWT Signed-off-by: Karol Szwaj * Add controller namespace to infra Signed-off-by: Karol Szwaj * Add Metadata envoy bootstrap struct Signed-off-by: Karol Szwaj * Add release note Signed-off-by: Karol Szwaj * fix lint Signed-off-by: Karol Szwaj * fix doc Signed-off-by: Karol Szwaj * use projected service account tokens with eg audience Signed-off-by: Karol Szwaj * lint code Signed-off-by: Karol Szwaj * make gen Signed-off-by: Karol Szwaj * make gen Signed-off-by: Karol Szwaj * Revert "Add controller namespace to infra" This reverts commit b2fa2caf58982432e5d5b31bd7d95a5ad523ed5e. Signed-off-by: Karol Szwaj * fetch the node id and initial metadata from first msg Signed-off-by: Karol Szwaj * update codegen Signed-off-by: Karol Szwaj * verify service account Signed-off-by: Huabing (Robin) Zhao * validate only sa Signed-off-by: Karol Szwaj * add local hash name func Signed-off-by: Karol Szwaj * Verify pod name for authz This reverts commit b0748a066c9f6a41920df95f728ace5f84ed1acb. Signed-off-by: Huabing (Robin) Zhao * lint code Signed-off-by: Karol Szwaj --------- Signed-off-by: Karol Szwaj Signed-off-by: Huabing (Robin) Zhao Co-authored-by: Huabing (Robin) Zhao Signed-off-by: Arko Dasgupta --- go.mod | 2 +- internal/infrastructure/common/proxy_args.go | 1 + .../kubernetes/proxy/resource.go | 33 ++++++++- .../kubernetes/proxy/resource_provider.go | 10 +-- .../proxy/resource_provider_test.go | 1 - .../testdata/daemonsets/component-level.yaml | 1 - .../proxy/testdata/daemonsets/custom.yaml | 1 - .../testdata/daemonsets/default-env.yaml | 1 - .../proxy/testdata/daemonsets/default.yaml | 1 - .../daemonsets/disable-prometheus.yaml | 1 - .../testdata/daemonsets/extension-env.yaml | 1 - .../override-labels-and-annotations.yaml | 1 - .../testdata/daemonsets/patch-daemonset.yaml | 1 - .../testdata/daemonsets/shutdown-manager.yaml | 1 - .../proxy/testdata/daemonsets/volumes.yaml | 1 - .../testdata/daemonsets/with-annotations.yaml | 1 - .../testdata/daemonsets/with-concurrency.yaml | 1 - .../testdata/daemonsets/with-extra-args.yaml | 1 - .../daemonsets/with-image-pull-secrets.yaml | 1 - .../proxy/testdata/daemonsets/with-name.yaml | 1 - .../daemonsets/with-node-selector.yaml | 1 - .../with-topology-spread-constraints.yaml | 1 - .../proxy/testdata/deployments/bootstrap.yaml | 1 - .../testdata/deployments/component-level.yaml | 1 - .../proxy/testdata/deployments/custom.yaml | 1 - .../custom_with_initcontainers.yaml | 1 - .../testdata/deployments/default-env.yaml | 1 - .../proxy/testdata/deployments/default.yaml | 1 - .../deployments/disable-prometheus.yaml | 1 - .../testdata/deployments/dual-stack.yaml | 1 - .../testdata/deployments/extension-env.yaml | 1 - .../deployments/gateway-namespace-mode.yaml | 14 +++- .../proxy/testdata/deployments/ipv6.yaml | 1 - .../override-labels-and-annotations.yaml | 1 - .../deployments/patch-deployment.yaml | 1 - .../deployments/shutdown-manager.yaml | 1 - .../proxy/testdata/deployments/volumes.yaml | 1 - .../deployments/with-annotations.yaml | 1 - .../deployments/with-concurrency.yaml | 1 - .../deployments/with-empty-memory-limits.yaml | 1 - .../testdata/deployments/with-extra-args.yaml | 1 - .../deployments/with-image-pull-secrets.yaml | 1 - .../proxy/testdata/deployments/with-name.yaml | 1 - .../deployments/with-node-selector.yaml | 1 - .../with-topology-spread-constraints.yaml | 1 - internal/xds/bootstrap/bootstrap.go | 3 +- internal/xds/bootstrap/bootstrap.yaml.tpl | 2 +- internal/xds/cache/snapshotcache.go | 13 ++++ internal/xds/server/kubejwt/jwtinterceptor.go | 73 ++++++++++++------- internal/xds/server/kubejwt/tokenreview.go | 45 +++++++++--- internal/xds/server/runner/runner.go | 3 +- release-notes/v1.4.0-rc.1.yaml | 1 + 52 files changed, 146 insertions(+), 94 deletions(-) diff --git a/go.mod b/go.mod index 03803c6c91..b424f48065 100644 --- a/go.mod +++ b/go.mod @@ -71,6 +71,7 @@ require ( k8s.io/api v0.33.0 k8s.io/apiextensions-apiserver v0.33.0 k8s.io/apimachinery v0.34.0-alpha.0 + k8s.io/apiserver v0.33.0 k8s.io/cli-runtime v0.33.0 k8s.io/client-go v0.33.0 k8s.io/klog/v2 v2.130.1 @@ -497,7 +498,6 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect honnef.co/go/tools v0.6.1 // indirect - k8s.io/apiserver v0.33.0 // indirect k8s.io/component-base v0.33.0 // indirect k8s.io/metrics v0.33.0 // indirect mvdan.cc/gofumpt v0.7.0 // indirect diff --git a/internal/infrastructure/common/proxy_args.go b/internal/infrastructure/common/proxy_args.go index 98a6aee524..8b490f0b14 100644 --- a/internal/infrastructure/common/proxy_args.go +++ b/internal/infrastructure/common/proxy_args.go @@ -33,6 +33,7 @@ func BuildProxyArgs( if bootstrapConfigOptions != nil && bootstrapConfigOptions.IPFamily == nil { bootstrapConfigOptions.IPFamily = getIPFamily(infra) } + bootstrapConfigOptions.GatewayNamespaceMode = gatewayNamespaceMode bootstrapConfigurations, err := bootstrap.GetRenderedBootstrapConfig(bootstrapConfigOptions) if err != nil { diff --git a/internal/infrastructure/kubernetes/proxy/resource.go b/internal/infrastructure/kubernetes/proxy/resource.go index 6dba6e525f..861275af9f 100644 --- a/internal/infrastructure/kubernetes/proxy/resource.go +++ b/internal/infrastructure/kubernetes/proxy/resource.go @@ -139,7 +139,7 @@ func expectedProxyContainers(infra *ir.ProxyInfra, Resources: *containerSpec.Resources, SecurityContext: expectedEnvoySecurityContext(containerSpec), Ports: ports, - VolumeMounts: expectedContainerVolumeMounts(containerSpec), + VolumeMounts: expectedContainerVolumeMounts(containerSpec, gatewayNamespaceMode), TerminationMessagePolicy: corev1.TerminationMessageReadFile, TerminationMessagePath: "/dev/termination-log", StartupProbe: &corev1.Probe{ @@ -288,7 +288,7 @@ func expectedShutdownPreStopCommand(cfg *egv1a1.ShutdownConfig) []string { } // expectedContainerVolumeMounts returns expected proxy container volume mounts. -func expectedContainerVolumeMounts(containerSpec *egv1a1.KubernetesContainerSpec) []corev1.VolumeMount { +func expectedContainerVolumeMounts(containerSpec *egv1a1.KubernetesContainerSpec, gatewayNamespaceMode bool) []corev1.VolumeMount { volumeMounts := []corev1.VolumeMount{ { Name: "certs", @@ -300,11 +300,19 @@ func expectedContainerVolumeMounts(containerSpec *egv1a1.KubernetesContainerSpec MountPath: "/sds", }, } + if gatewayNamespaceMode { + volumeMounts = append(volumeMounts, corev1.VolumeMount{ + Name: "sa-token", + MountPath: "/var/run/secrets/token", + ReadOnly: true, + }) + } + return resource.ExpectedContainerVolumeMounts(containerSpec, volumeMounts) } // expectedVolumes returns expected proxy deployment volumes. -func expectedVolumes(name string, gatewayNamespacedMode bool, pod *egv1a1.KubernetesPodSpec) []corev1.Volume { +func expectedVolumes(name string, gatewayNamespacedMode bool, pod *egv1a1.KubernetesPodSpec, dnsDomain string) []corev1.Volume { var volumes []corev1.Volume certsVolume := corev1.Volume{ Name: "certs", @@ -335,6 +343,25 @@ func expectedVolumes(name string, gatewayNamespacedMode bool, pod *egv1a1.Kubern }, }, } + saAudience := fmt.Sprintf("%s.%s.svc.%s", config.EnvoyGatewayServiceName, config.DefaultNamespace, dnsDomain) + saTokenProjectedVolume := corev1.Volume{ + Name: "sa-token", + VolumeSource: corev1.VolumeSource{ + Projected: &corev1.ProjectedVolumeSource{ + Sources: []corev1.VolumeProjection{ + { + ServiceAccountToken: &corev1.ServiceAccountTokenProjection{ + Path: "sa-token", + Audience: saAudience, + ExpirationSeconds: ptr.To[int64](3600), + }, + }, + }, + DefaultMode: ptr.To[int32](420), + }, + }, + } + volumes = append(volumes, saTokenProjectedVolume) } volumes = append(volumes, certsVolume) diff --git a/internal/infrastructure/kubernetes/proxy/resource_provider.go b/internal/infrastructure/kubernetes/proxy/resource_provider.go index 0c8b5e16d6..dacb399429 100644 --- a/internal/infrastructure/kubernetes/proxy/resource_provider.go +++ b/internal/infrastructure/kubernetes/proxy/resource_provider.go @@ -322,7 +322,6 @@ func (r *ResourceRender) Deployment() (*appsv1.Deployment, error) { Containers: containers, InitContainers: deploymentConfig.InitContainers, ServiceAccountName: r.Name(), - AutomountServiceAccountToken: expectedAutoMountServiceAccountToken(r.GatewayNamespaceMode), TerminationGracePeriodSeconds: expectedTerminationGracePeriodSeconds(proxyConfig.Spec.Shutdown), DNSPolicy: corev1.DNSClusterFirst, RestartPolicy: corev1.RestartPolicyAlways, @@ -330,7 +329,7 @@ func (r *ResourceRender) Deployment() (*appsv1.Deployment, error) { SecurityContext: deploymentConfig.Pod.SecurityContext, Affinity: deploymentConfig.Pod.Affinity, Tolerations: deploymentConfig.Pod.Tolerations, - Volumes: expectedVolumes(r.infra.Name, r.GatewayNamespaceMode, deploymentConfig.Pod), + Volumes: expectedVolumes(r.infra.Name, r.GatewayNamespaceMode, deploymentConfig.Pod, r.DNSDomain), ImagePullSecrets: deploymentConfig.Pod.ImagePullSecrets, NodeSelector: deploymentConfig.Pod.NodeSelector, TopologySpreadConstraints: deploymentConfig.Pod.TopologySpreadConstraints, @@ -537,10 +536,6 @@ func expectedTerminationGracePeriodSeconds(cfg *egv1a1.ShutdownConfig) *int64 { return ptr.To(int64(s)) } -func expectedAutoMountServiceAccountToken(gatewayNamespacedMode bool) *bool { - return ptr.To(gatewayNamespacedMode) -} - func (r *ResourceRender) getPodSpec( containers, initContainers []corev1.Container, pod *egv1a1.KubernetesPodSpec, @@ -550,7 +545,6 @@ func (r *ResourceRender) getPodSpec( Containers: containers, InitContainers: initContainers, ServiceAccountName: ExpectedResourceHashedName(r.infra.Name), - AutomountServiceAccountToken: expectedAutoMountServiceAccountToken(r.GatewayNamespaceMode), TerminationGracePeriodSeconds: expectedTerminationGracePeriodSeconds(proxyConfig.Spec.Shutdown), DNSPolicy: corev1.DNSClusterFirst, RestartPolicy: corev1.RestartPolicyAlways, @@ -558,7 +552,7 @@ func (r *ResourceRender) getPodSpec( SecurityContext: pod.SecurityContext, Affinity: pod.Affinity, Tolerations: pod.Tolerations, - Volumes: expectedVolumes(r.infra.Name, r.GatewayNamespaceMode, pod), + Volumes: expectedVolumes(r.infra.Name, r.GatewayNamespaceMode, pod, r.DNSDomain), ImagePullSecrets: pod.ImagePullSecrets, NodeSelector: pod.NodeSelector, TopologySpreadConstraints: pod.TopologySpreadConstraints, diff --git a/internal/infrastructure/kubernetes/proxy/resource_provider_test.go b/internal/infrastructure/kubernetes/proxy/resource_provider_test.go index 88899754de..2fd4ed406b 100644 --- a/internal/infrastructure/kubernetes/proxy/resource_provider_test.go +++ b/internal/infrastructure/kubernetes/proxy/resource_provider_test.go @@ -1329,7 +1329,6 @@ func TestServiceAccount(t *testing.T) { ns = tc.infra.GetProxyInfra().Namespace } r := NewResourceRender(ns, cfg.DNSDomain, tc.infra.GetProxyInfra(), cfg.EnvoyGateway) - sa, err := r.ServiceAccount() require.NoError(t, err) diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/component-level.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/component-level.yaml index ae7b709f6d..202550e3ab 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/component-level.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/component-level.yaml @@ -32,7 +32,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/custom.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/custom.yaml index 46a6b4dbed..01dc903d77 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/custom.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/custom.yaml @@ -33,7 +33,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default-env.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default-env.yaml index c8b7570501..6f3e23e5b6 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default-env.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default-env.yaml @@ -32,7 +32,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default.yaml index ed33f0d6db..bfff34fe21 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default.yaml @@ -32,7 +32,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/disable-prometheus.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/disable-prometheus.yaml index ea4a554ba4..16ff0118a1 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/disable-prometheus.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/disable-prometheus.yaml @@ -28,7 +28,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/extension-env.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/extension-env.yaml index 344b3d3229..d12f8f3f6a 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/extension-env.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/extension-env.yaml @@ -32,7 +32,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml index ed44da4123..17c08ce3d3 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml @@ -41,7 +41,6 @@ spec: label1: value1-override label2: value2 spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/patch-daemonset.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/patch-daemonset.yaml index b0ecbe7bea..589777f72d 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/patch-daemonset.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/patch-daemonset.yaml @@ -32,7 +32,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/shutdown-manager.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/shutdown-manager.yaml index 0d6539199c..991f73891e 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/shutdown-manager.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/shutdown-manager.yaml @@ -32,7 +32,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/volumes.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/volumes.yaml index eb22669380..34a02349ba 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/volumes.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/volumes.yaml @@ -32,7 +32,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-annotations.yaml index 846cf09502..6756279087 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-annotations.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-annotations.yaml @@ -37,7 +37,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-concurrency.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-concurrency.yaml index 6d526e1500..d37b18efe4 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-concurrency.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-concurrency.yaml @@ -32,7 +32,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-extra-args.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-extra-args.yaml index 52a4bc724f..60af1ff399 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-extra-args.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-extra-args.yaml @@ -32,7 +32,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-image-pull-secrets.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-image-pull-secrets.yaml index 6b8aea93a0..8ff94e418d 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-image-pull-secrets.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-image-pull-secrets.yaml @@ -32,7 +32,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-name.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-name.yaml index 94cb3d5bf9..2f100f2ca4 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-name.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-name.yaml @@ -32,7 +32,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-node-selector.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-node-selector.yaml index 5dd4d9d20b..479bd3f92c 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-node-selector.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-node-selector.yaml @@ -32,7 +32,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-topology-spread-constraints.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-topology-spread-constraints.yaml index 799cd00c8b..7a1ec1637f 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-topology-spread-constraints.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-topology-spread-constraints.yaml @@ -32,7 +32,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/bootstrap.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/bootstrap.yaml index 62f20843ac..1979ed689e 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/bootstrap.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/bootstrap.yaml @@ -36,7 +36,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/component-level.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/component-level.yaml index c80dd9a4fa..588020931d 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/component-level.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/component-level.yaml @@ -36,7 +36,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom.yaml index 60676d3903..bfe499c13c 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom.yaml @@ -38,7 +38,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom_with_initcontainers.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom_with_initcontainers.yaml index ed1b655c87..0c1203599d 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom_with_initcontainers.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom_with_initcontainers.yaml @@ -38,7 +38,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/default-env.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/default-env.yaml index fa2b709e16..3452e2a61a 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/default-env.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/default-env.yaml @@ -37,7 +37,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/default.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/default.yaml index 29b60d073c..bdb8c2cdec 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/default.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/default.yaml @@ -36,7 +36,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/disable-prometheus.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/disable-prometheus.yaml index 94fda682b3..911b4da2c4 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/disable-prometheus.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/disable-prometheus.yaml @@ -32,7 +32,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/dual-stack.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/dual-stack.yaml index 7a850e68c8..e9cf766e98 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/dual-stack.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/dual-stack.yaml @@ -36,7 +36,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/extension-env.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/extension-env.yaml index cc8e5d1c6c..17a67a3e5f 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/extension-env.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/extension-env.yaml @@ -37,7 +37,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/gateway-namespace-mode.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/gateway-namespace-mode.yaml index 2d8a29078f..731527d592 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/gateway-namespace-mode.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/gateway-namespace-mode.yaml @@ -36,7 +36,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: true containers: - args: - --service-cluster default @@ -243,7 +242,7 @@ spec: - name: jwt-sa-bearer generic_secret: secret: - filename: "/var/run/secrets/kubernetes.io/serviceaccount/token" + filename: "/var/run/secrets/token/sa-token" overload_manager: refresh_interval: 0.25s resource_monitors: @@ -336,6 +335,9 @@ spec: readOnly: true - mountPath: /sds name: sds + - mountPath: /var/run/secrets/token + name: sa-token + readOnly: true - args: - envoy - shutdown-manager @@ -414,6 +416,14 @@ spec: serviceAccountName: envoy-default-37a8eec1 terminationGracePeriodSeconds: 360 volumes: + - name: sa-token + projected: + defaultMode: 420 + sources: + - serviceAccountToken: + audience: envoy-gateway.envoy-gateway-system.svc.cluster.local + expirationSeconds: 3600 + path: sa-token - configMap: defaultMode: 420 items: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/ipv6.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/ipv6.yaml index cf1932a2d6..6ac2eb10f3 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/ipv6.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/ipv6.yaml @@ -36,7 +36,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml index 8c32258bdc..1800214497 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml @@ -45,7 +45,6 @@ spec: label1: value1-override label2: value2 spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/patch-deployment.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/patch-deployment.yaml index d058555584..710b667be1 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/patch-deployment.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/patch-deployment.yaml @@ -36,7 +36,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/shutdown-manager.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/shutdown-manager.yaml index e231ba2bb2..455d77c8c8 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/shutdown-manager.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/shutdown-manager.yaml @@ -36,7 +36,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/volumes.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/volumes.yaml index 896a680709..853165e1db 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/volumes.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/volumes.yaml @@ -37,7 +37,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-annotations.yaml index 0c57f4c82b..0a2db99917 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-annotations.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-annotations.yaml @@ -41,7 +41,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-concurrency.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-concurrency.yaml index 2c68a3bbfd..fe0bfbc431 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-concurrency.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-concurrency.yaml @@ -36,7 +36,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-empty-memory-limits.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-empty-memory-limits.yaml index 808e331771..8fc16b444b 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-empty-memory-limits.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-empty-memory-limits.yaml @@ -36,7 +36,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-extra-args.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-extra-args.yaml index bde6f343dc..d41cf41ad9 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-extra-args.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-extra-args.yaml @@ -36,7 +36,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-image-pull-secrets.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-image-pull-secrets.yaml index 2dd70ff619..cf8b0b6e92 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-image-pull-secrets.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-image-pull-secrets.yaml @@ -36,7 +36,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-name.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-name.yaml index aabd4640fa..92266c3f0f 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-name.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-name.yaml @@ -36,7 +36,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-node-selector.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-node-selector.yaml index 9aaedf43c9..7283f66ec1 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-node-selector.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-node-selector.yaml @@ -36,7 +36,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-topology-spread-constraints.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-topology-spread-constraints.yaml index 6a18de2db9..805c97b906 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-topology-spread-constraints.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-topology-spread-constraints.yaml @@ -36,7 +36,6 @@ spec: gateway.envoyproxy.io/owning-gateway-name: default gateway.envoyproxy.io/owning-gateway-namespace: default spec: - automountServiceAccountToken: false containers: - args: - --service-cluster default diff --git a/internal/xds/bootstrap/bootstrap.go b/internal/xds/bootstrap/bootstrap.go index 04d847ee76..3818dab3b7 100644 --- a/internal/xds/bootstrap/bootstrap.go +++ b/internal/xds/bootstrap/bootstrap.go @@ -98,7 +98,8 @@ type bootstrapParameters struct { OverloadManager overloadManagerParameters // IPFamily of the Listener - IPFamily string + IPFamily string + // GatewayNamespaceMode defines whether to use the Envoy Gateway namespace mode. GatewayNamespaceMode bool } diff --git a/internal/xds/bootstrap/bootstrap.yaml.tpl b/internal/xds/bootstrap/bootstrap.yaml.tpl index 3df05d4fd8..c4bb5e4843 100644 --- a/internal/xds/bootstrap/bootstrap.yaml.tpl +++ b/internal/xds/bootstrap/bootstrap.yaml.tpl @@ -309,7 +309,7 @@ static_resources: - name: jwt-sa-bearer generic_secret: secret: - filename: "/var/run/secrets/kubernetes.io/serviceaccount/token" + filename: "/var/run/secrets/token/sa-token" {{- end }} overload_manager: refresh_interval: 0.25s diff --git a/internal/xds/cache/snapshotcache.go b/internal/xds/cache/snapshotcache.go index 50ae0fcae7..3a88c8a84f 100644 --- a/internal/xds/cache/snapshotcache.go +++ b/internal/xds/cache/snapshotcache.go @@ -48,6 +48,7 @@ type SnapshotCacheWithCallbacks interface { serverv3.Callbacks GenerateNewSnapshot(string, types.XdsResources) error SnapshotHasIrKey(string) bool + GetIrKeys() []string } type snapshotMap map[string]*cachev3.Snapshot @@ -377,3 +378,15 @@ func (s *snapshotCache) SnapshotHasIrKey(irKey string) bool { return false } + +func (s *snapshotCache) GetIrKeys() []string { + s.mu.Lock() + defer s.mu.Unlock() + + var irKeys []string + for key := range s.lastSnapshot { + irKeys = append(irKeys, key) + } + + return irKeys +} diff --git a/internal/xds/server/kubejwt/jwtinterceptor.go b/internal/xds/server/kubejwt/jwtinterceptor.go index 4f5c73d499..6962f9ea25 100644 --- a/internal/xds/server/kubejwt/jwtinterceptor.go +++ b/internal/xds/server/kubejwt/jwtinterceptor.go @@ -10,11 +10,11 @@ import ( "fmt" "strings" + discoveryv3 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" "google.golang.org/grpc" "google.golang.org/grpc/metadata" "k8s.io/client-go/kubernetes" - "github.com/envoyproxy/gateway/internal/message" "github.com/envoyproxy/gateway/internal/xds/cache" ) @@ -22,47 +22,70 @@ import ( type JWTAuthInterceptor struct { clientset *kubernetes.Clientset issuer string + audience string cache cache.SnapshotCacheWithCallbacks - xds *message.Xds } // NewJWTAuthInterceptor initializes a new JWTAuthInterceptor. -func NewJWTAuthInterceptor(clientset *kubernetes.Clientset, issuer string, cache cache.SnapshotCacheWithCallbacks, xds *message.Xds) *JWTAuthInterceptor { +func NewJWTAuthInterceptor(clientset *kubernetes.Clientset, issuer, audience string, cache cache.SnapshotCacheWithCallbacks) *JWTAuthInterceptor { return &JWTAuthInterceptor{ clientset: clientset, issuer: issuer, + audience: audience, cache: cache, - xds: xds, } } -// Stream intercepts streaming gRPC calls for authentication. -func (i *JWTAuthInterceptor) Stream() grpc.StreamServerInterceptor { - return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { - if err := i.authorize(ss.Context()); err != nil { - return err - } - return handler(srv, ss) - } +type wrappedStream struct { + grpc.ServerStream + ctx context.Context + interceptor *JWTAuthInterceptor + validated bool } -// authorize validates the Kubernetes Service Account JWT token from the metadata. -func (i *JWTAuthInterceptor) authorize(ctx context.Context) error { - md, ok := metadata.FromIncomingContext(ctx) - if !ok { - return fmt.Errorf("missing metadata") +func (w *wrappedStream) RecvMsg(m any) error { + err := w.ServerStream.RecvMsg(m) + if err != nil { + return err } - authHeader, exists := md["authorization"] - if !exists || len(authHeader) == 0 { - return fmt.Errorf("missing authorization token in metadata: %s", md) - } + if !w.validated { + if req, ok := m.(*discoveryv3.DeltaDiscoveryRequest); ok { + if req.Node == nil || req.Node.Id == "" { + return fmt.Errorf("missing node ID in request") + } + nodeID := req.Node.Id - tokenStr := strings.TrimPrefix(authHeader[0], "Bearer ") - err := i.validateKubeJWT(ctx, tokenStr) - if err != nil { - return fmt.Errorf("failed to validate token: %w", err) + md, ok := metadata.FromIncomingContext(w.ctx) + if !ok { + return fmt.Errorf("missing metadata") + } + + authHeader := md.Get("authorization") + if len(authHeader) == 0 { + return fmt.Errorf("missing authorization token in metadata: %s", md) + } + token := strings.TrimPrefix(authHeader[0], "Bearer ") + + if err := w.interceptor.validateKubeJWT(w.ctx, token, nodeID); err != nil { + return fmt.Errorf("failed to validate token: %w", err) + } + + w.validated = true + } } return nil } + +func newWrappedStream(s grpc.ServerStream, ctx context.Context, interceptor *JWTAuthInterceptor) grpc.ServerStream { + return &wrappedStream{s, ctx, interceptor, false} +} + +// Stream intercepts streaming gRPC calls for authorization. +func (i *JWTAuthInterceptor) Stream() grpc.StreamServerInterceptor { + return func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + wrapped := newWrappedStream(ss, ss.Context(), i) + return handler(srv, wrapped) + } +} diff --git a/internal/xds/server/kubejwt/tokenreview.go b/internal/xds/server/kubejwt/tokenreview.go index 6ecff11b83..efed1e7239 100644 --- a/internal/xds/server/kubejwt/tokenreview.go +++ b/internal/xds/server/kubejwt/tokenreview.go @@ -13,12 +13,12 @@ import ( authenticationv1 "k8s.io/api/authentication/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apiserver/pkg/authentication/serviceaccount" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" -) -const ( - authPodNameKey = "authentication.kubernetes.io/pod-name" + "github.com/envoyproxy/gateway/internal/envoygateway/config" + "github.com/envoyproxy/gateway/internal/utils" ) // GetKubernetesClient creates a Kubernetes client using in-cluster configuration. @@ -36,10 +36,11 @@ func GetKubernetesClient() (*kubernetes.Clientset, error) { return clientset, nil } -func (i *JWTAuthInterceptor) validateKubeJWT(ctx context.Context, token string) error { +func (i *JWTAuthInterceptor) validateKubeJWT(ctx context.Context, token, nodeID string) error { tokenReview := &authenticationv1.TokenReview{ Spec: authenticationv1.TokenReviewSpec{ - Token: token, + Token: token, + Audiences: []string{i.audience}, }, } @@ -56,19 +57,39 @@ func (i *JWTAuthInterceptor) validateKubeJWT(ctx context.Context, token string) return fmt.Errorf("token is not authenticated") } - // TODO: (cnvergence) define a better way to check if the token is coming from the correct node + // Check if the node ID in the request matches the pod name in the token review response. + // This is used to prevent a client from accessing the xDS resource of another one. if tokenReview.Status.User.Extra != nil { - podName := tokenReview.Status.User.Extra[authPodNameKey] + podName := tokenReview.Status.User.Extra[serviceaccount.PodNameKey] if podName[0] == "" { return fmt.Errorf("pod name not found in token review response") } - parts := strings.Split(podName[0], "-") - irKey := fmt.Sprintf("%s/%s", parts[1], parts[2]) - if !i.cache.SnapshotHasIrKey(irKey) { - return fmt.Errorf("pod %s not found in cache", podName) + if podName[0] != nodeID { + return fmt.Errorf("pod name mismatch: expected %s, got %s", nodeID, podName[0]) } } - return nil + // Check if the service account name in the JWT token exists in the cache. + // This is used to verify that the token belongs to a valid Enovy managed by Envoy Gateway. + // example: "system:serviceaccount:default:envoy-default-eg-e41e7b31" + parts := strings.Split(tokenReview.Status.User.Username, ":") + if len(parts) != 4 { + return fmt.Errorf("invalid username format: %s", tokenReview.Status.User.Username) + } + sa := parts[3] + + irKeys := i.cache.GetIrKeys() + for _, irKey := range irKeys { + if irKey2ServiceAccountName(irKey) == sa { + return nil + } + } + return fmt.Errorf("Envoy service account %s not found in the cache", sa) +} + +// this is the same logic used in infra pkg func ExpectedResourceHashedName to generate the resource name. +func irKey2ServiceAccountName(irKey string) string { + hashedName := utils.GetHashedName(irKey, 48) + return fmt.Sprintf("%s-%s", config.EnvoyPrefix, hashedName) } diff --git a/internal/xds/server/runner/runner.go b/internal/xds/server/runner/runner.go index e1343a0134..46001cd3e7 100644 --- a/internal/xds/server/runner/runner.go +++ b/internal/xds/server/runner/runner.go @@ -112,11 +112,12 @@ func (r *Runner) Start(ctx context.Context) (err error) { if err != nil { return fmt.Errorf("failed to create Kubernetes client: %w", err) } + saAudience := fmt.Sprintf("%s.%s.svc.%s", config.EnvoyGatewayServiceName, r.ControllerNamespace, r.DNSDomain) jwtInterceptor := kubejwt.NewJWTAuthInterceptor( clientset, defaultKubernetesIssuer, + saAudience, r.cache, - r.Xds, ) creds, err := credentials.NewServerTLSFromFile(xdsTLSCertFilepath, xdsTLSKeyFilepath) diff --git a/release-notes/v1.4.0-rc.1.yaml b/release-notes/v1.4.0-rc.1.yaml index b4a4fdb37f..483d4093c7 100644 --- a/release-notes/v1.4.0-rc.1.yaml +++ b/release-notes/v1.4.0-rc.1.yaml @@ -40,6 +40,7 @@ new features: | Added support for request buffering using the Envoy Buffer filter Added support for merge type in BackendTrafficPolicy Added support for `OverlappingTLSConfig` condition in Gateway status. This condition is set if there are overlapping hostnames or certificates between listeners. The ALPN protocol is set to HTTP/1.1 for the overlapping listeners to avoid HTTP/2 Connection Coalescing. + Added support for running Envoy infrastructure proxies in the Gateway namespace. bug fixes: | Fix traffic splitting when filters are attached to the backendRef. From 2d81be292b8c1767a5803333c8ace700a733dafe Mon Sep 17 00:00:00 2001 From: Karol Szwaj Date: Fri, 9 May 2025 12:09:09 +0200 Subject: [PATCH 38/66] feat: add controller namespace field to infrastructure render (#5937) * Add controller namespace to infra Signed-off-by: Karol Szwaj * make gen Signed-off-by: Karol Szwaj * rebase code and add controller namespace helper Signed-off-by: Karol Szwaj * rename to envoy namespace Signed-off-by: Karol Szwaj * rename to ControllerNamespace Signed-off-by: Karol Szwaj --------- Signed-off-by: Karol Szwaj Signed-off-by: Arko Dasgupta --- internal/infrastructure/kubernetes/infra.go | 14 +++---- .../kubernetes/proxy/resource.go | 40 ++++--------------- .../kubernetes/proxy/resource_provider.go | 22 ++++++---- .../proxy/resource_provider_test.go | 23 ++++++----- .../testdata/daemonsets/component-level.yaml | 10 +---- .../proxy/testdata/daemonsets/custom.yaml | 10 +---- .../testdata/daemonsets/default-env.yaml | 10 +---- .../proxy/testdata/daemonsets/default.yaml | 10 +---- .../daemonsets/disable-prometheus.yaml | 10 +---- .../testdata/daemonsets/extension-env.yaml | 10 +---- .../override-labels-and-annotations.yaml | 10 +---- .../testdata/daemonsets/patch-daemonset.yaml | 10 +---- .../testdata/daemonsets/shutdown-manager.yaml | 10 +---- .../proxy/testdata/daemonsets/volumes.yaml | 10 +---- .../testdata/daemonsets/with-annotations.yaml | 10 +---- .../testdata/daemonsets/with-concurrency.yaml | 10 +---- .../testdata/daemonsets/with-extra-args.yaml | 10 +---- .../daemonsets/with-image-pull-secrets.yaml | 10 +---- .../proxy/testdata/daemonsets/with-name.yaml | 10 +---- .../daemonsets/with-node-selector.yaml | 10 +---- .../with-topology-spread-constraints.yaml | 10 +---- .../proxy/testdata/deployments/bootstrap.yaml | 10 +---- .../testdata/deployments/component-level.yaml | 10 +---- .../proxy/testdata/deployments/custom.yaml | 10 +---- .../custom_with_initcontainers.yaml | 10 +---- .../testdata/deployments/default-env.yaml | 10 +---- .../proxy/testdata/deployments/default.yaml | 10 +---- .../deployments/disable-prometheus.yaml | 10 +---- .../testdata/deployments/dual-stack.yaml | 10 +---- .../testdata/deployments/extension-env.yaml | 10 +---- .../deployments/gateway-namespace-mode.yaml | 10 ++--- .../proxy/testdata/deployments/ipv6.yaml | 10 +---- .../override-labels-and-annotations.yaml | 10 +---- .../deployments/patch-deployment.yaml | 10 +---- .../deployments/shutdown-manager.yaml | 10 +---- .../proxy/testdata/deployments/volumes.yaml | 10 +---- .../deployments/with-annotations.yaml | 10 +---- .../deployments/with-concurrency.yaml | 10 +---- .../deployments/with-empty-memory-limits.yaml | 10 +---- .../testdata/deployments/with-extra-args.yaml | 10 +---- .../deployments/with-image-pull-secrets.yaml | 10 +---- .../proxy/testdata/deployments/with-name.yaml | 10 +---- .../deployments/with-node-selector.yaml | 10 +---- .../with-topology-spread-constraints.yaml | 10 +---- .../gateway-namespace-mode.yaml | 2 +- .../kubernetes/proxy_configmap_test.go | 6 +-- .../kubernetes/proxy_daemonset_test.go | 6 +-- .../kubernetes/proxy_deployment_test.go | 10 ++--- .../infrastructure/kubernetes/proxy_infra.go | 10 ++--- .../kubernetes/proxy_infra_test.go | 8 ++-- .../kubernetes/proxy_service_test.go | 2 +- .../kubernetes/proxy_serviceaccount_test.go | 6 +-- .../kubernetes/ratelimit_deployment_test.go | 6 +-- .../kubernetes/ratelimit_infra.go | 8 ++-- .../kubernetes/ratelimit_infra_test.go | 12 +++--- .../kubernetes/ratelimit_service_test.go | 2 +- .../ratelimit_serviceaccount_test.go | 6 +-- 57 files changed, 167 insertions(+), 416 deletions(-) diff --git a/internal/infrastructure/kubernetes/infra.go b/internal/infrastructure/kubernetes/infra.go index 9c460775f2..0cde2e2c50 100644 --- a/internal/infrastructure/kubernetes/infra.go +++ b/internal/infrastructure/kubernetes/infra.go @@ -45,8 +45,8 @@ type ResourceRender interface { // Infra manages the creation and deletion of Kubernetes infrastructure // based on Infra IR resources. type Infra struct { - // Namespace is the Namespace used for managed infra. - Namespace string + // ControllerNamespace is the namespace where Envoy Gateway is deployed. + ControllerNamespace string // DNSDomain is the dns domain used by k8s services. Defaults to "cluster.local". DNSDomain string @@ -65,11 +65,11 @@ func NewInfra(cli client.Client, cfg *config.Server) *Infra { return &Infra{ // Always set infra namespace to cfg.ControllerNamespace, // Otherwise RateLimit resource provider will failed to create/delete. - Namespace: cfg.ControllerNamespace, - DNSDomain: cfg.DNSDomain, - EnvoyGateway: cfg.EnvoyGateway, - Client: New(cli), - logger: cfg.Logger.WithName(string(egv1a1.LogComponentInfrastructureRunner)), + ControllerNamespace: cfg.ControllerNamespace, + DNSDomain: cfg.DNSDomain, + EnvoyGateway: cfg.EnvoyGateway, + Client: New(cli), + logger: cfg.Logger.WithName(string(egv1a1.LogComponentInfrastructureRunner)), } } diff --git a/internal/infrastructure/kubernetes/proxy/resource.go b/internal/infrastructure/kubernetes/proxy/resource.go index 861275af9f..fcce7c0e43 100644 --- a/internal/infrastructure/kubernetes/proxy/resource.go +++ b/internal/infrastructure/kubernetes/proxy/resource.go @@ -84,7 +84,7 @@ func enablePrometheus(infra *ir.ProxyInfra) bool { func expectedProxyContainers(infra *ir.ProxyInfra, containerSpec *egv1a1.KubernetesContainerSpec, shutdownConfig *egv1a1.ShutdownConfig, shutdownManager *egv1a1.ShutdownManager, - egNamespace, dnsDomain string, gatewayNamespaceMode bool, + controllerNamespace, dnsDomain string, gatewayNamespaceMode bool, ) ([]corev1.Container, error) { ports := make([]corev1.ContainerPort, 0, 2) if enablePrometheus(infra) { @@ -108,10 +108,6 @@ func expectedProxyContainers(infra *ir.ProxyInfra, } maxHeapSizeBytes := calculateMaxHeapSizeBytes(containerSpec.Resources) - - if gatewayNamespaceMode { - egNamespace = config.DefaultNamespace - } // Get the default Bootstrap bootstrapConfigOptions := &bootstrap.RenderBootstrapConfigOptions{ ProxyMetrics: proxyMetrics, @@ -120,7 +116,7 @@ func expectedProxyContainers(infra *ir.ProxyInfra, TrustedCA: filepath.Join("/sds", common.SdsCAFilename), }, MaxHeapSizeBytes: maxHeapSizeBytes, - XdsServerHost: ptr.To(fmt.Sprintf("%s.%s.svc.%s", config.EnvoyGatewayServiceName, egNamespace, dnsDomain)), + XdsServerHost: ptr.To(fmt.Sprintf("%s.%s.svc.%s", config.EnvoyGatewayServiceName, controllerNamespace, dnsDomain)), } args, err := common.BuildProxyArgs(infra, shutdownConfig, bootstrapConfigOptions, fmt.Sprintf("$(%s)", envoyPodEnvVar), gatewayNamespaceMode) @@ -135,7 +131,7 @@ func expectedProxyContainers(infra *ir.ProxyInfra, ImagePullPolicy: corev1.PullIfNotPresent, Command: []string{"envoy"}, Args: args, - Env: expectedContainerEnv(containerSpec, gatewayNamespaceMode), + Env: expectedContainerEnv(containerSpec, controllerNamespace), Resources: *containerSpec.Resources, SecurityContext: expectedEnvoySecurityContext(containerSpec), Ports: ports, @@ -197,7 +193,7 @@ func expectedProxyContainers(infra *ir.ProxyInfra, ImagePullPolicy: corev1.PullIfNotPresent, Command: []string{"envoy-gateway"}, Args: expectedShutdownManagerArgs(shutdownConfig), - Env: expectedContainerEnv(nil, gatewayNamespaceMode), + Env: expectedContainerEnv(nil, controllerNamespace), Resources: *egv1a1.DefaultShutdownManagerContainerResourceRequirements(), TerminationMessagePolicy: corev1.TerminationMessageReadFile, TerminationMessagePath: "/dev/termination-log", @@ -413,16 +409,11 @@ func expectedVolumes(name string, gatewayNamespacedMode bool, pod *egv1a1.Kubern } // expectedContainerEnv returns expected proxy container envs. -func expectedContainerEnv(containerSpec *egv1a1.KubernetesContainerSpec, gatewayNamespaceMode bool) []corev1.EnvVar { +func expectedContainerEnv(containerSpec *egv1a1.KubernetesContainerSpec, controllerNamespace string) []corev1.EnvVar { env := []corev1.EnvVar{ { - Name: envoyNsEnvVar, - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: "metadata.namespace", - }, - }, + Name: envoyNsEnvVar, + Value: controllerNamespace, }, { Name: envoyZoneEnvVar, @@ -434,23 +425,6 @@ func expectedContainerEnv(containerSpec *egv1a1.KubernetesContainerSpec, gateway }, }, } - if gatewayNamespaceMode { - env = []corev1.EnvVar{ - { - Name: envoyNsEnvVar, - Value: config.DefaultNamespace, - }, - { - Name: envoyZoneEnvVar, - ValueFrom: &corev1.EnvVarSource{ - FieldRef: &corev1.ObjectFieldSelector{ - APIVersion: "v1", - FieldPath: fmt.Sprintf("metadata.labels['%s']", corev1.LabelTopologyZone), - }, - }, - }, - } - } env = append(env, corev1.EnvVar{ Name: envoyPodEnvVar, diff --git a/internal/infrastructure/kubernetes/proxy/resource_provider.go b/internal/infrastructure/kubernetes/proxy/resource_provider.go index dacb399429..75d6397223 100644 --- a/internal/infrastructure/kubernetes/proxy/resource_provider.go +++ b/internal/infrastructure/kubernetes/proxy/resource_provider.go @@ -46,8 +46,11 @@ const ( type ResourceRender struct { infra *ir.ProxyInfra - // namespace is the Namespace used for managed infra. - namespace string + // envoyNamespace is the namespace used for managed infra. + envoyNamespace string + + // controllerNamespace is the namespace used for Envoy Gateway controller. + controllerNamespace string // DNSDomain is the dns domain used by k8s services. Defaults to "cluster.local". DNSDomain string @@ -57,9 +60,10 @@ type ResourceRender struct { GatewayNamespaceMode bool } -func NewResourceRender(ns, dnsDomain string, infra *ir.ProxyInfra, gateway *egv1a1.EnvoyGateway) *ResourceRender { +func NewResourceRender(envoyNamespace, controllerNamespace, dnsDomain string, infra *ir.ProxyInfra, gateway *egv1a1.EnvoyGateway) *ResourceRender { return &ResourceRender{ - namespace: ns, + envoyNamespace: envoyNamespace, + controllerNamespace: controllerNamespace, DNSDomain: dnsDomain, infra: infra, ShutdownManager: gateway.GetEnvoyGatewayProvider().GetEnvoyGatewayKubeProvider().ShutdownManager, @@ -72,7 +76,11 @@ func (r *ResourceRender) Name() string { } func (r *ResourceRender) Namespace() string { - return r.namespace + return r.envoyNamespace +} + +func (r *ResourceRender) ControllerNamespace() string { + return r.controllerNamespace } func (r *ResourceRender) LabelSelector() labels.Selector { @@ -284,7 +292,7 @@ func (r *ResourceRender) Deployment() (*appsv1.Deployment, error) { } // Get expected bootstrap configurations rendered ProxyContainers - containers, err := expectedProxyContainers(r.infra, deploymentConfig.Container, proxyConfig.Spec.Shutdown, r.ShutdownManager, r.Namespace(), r.DNSDomain, r.GatewayNamespaceMode) + containers, err := expectedProxyContainers(r.infra, deploymentConfig.Container, proxyConfig.Spec.Shutdown, r.ShutdownManager, r.ControllerNamespace(), r.DNSDomain, r.GatewayNamespaceMode) if err != nil { return nil, err } @@ -372,7 +380,7 @@ func (r *ResourceRender) DaemonSet() (*appsv1.DaemonSet, error) { } // Get expected bootstrap configurations rendered ProxyContainers - containers, err := expectedProxyContainers(r.infra, daemonSetConfig.Container, proxyConfig.Spec.Shutdown, r.ShutdownManager, r.Namespace(), r.DNSDomain, r.GatewayNamespaceMode) + containers, err := expectedProxyContainers(r.infra, daemonSetConfig.Container, proxyConfig.Spec.Shutdown, r.ShutdownManager, r.ControllerNamespace(), r.DNSDomain, r.GatewayNamespaceMode) if err != nil { return nil, err } diff --git a/internal/infrastructure/kubernetes/proxy/resource_provider_test.go b/internal/infrastructure/kubernetes/proxy/resource_provider_test.go index 2fd4ed406b..3d9b5ef316 100644 --- a/internal/infrastructure/kubernetes/proxy/resource_provider_test.go +++ b/internal/infrastructure/kubernetes/proxy/resource_provider_test.go @@ -44,8 +44,11 @@ func newTestInfra() *ir.Infra { } func newTestInfraWithNamespace(namespace string) *ir.Infra { - i := newTestInfra() + i := ir.NewInfra() i.Proxy.Namespace = namespace + i.Proxy.GetProxyMetadata().Labels[gatewayapi.OwningGatewayNamespaceLabel] = namespace + i.Proxy.GetProxyMetadata().Labels[gatewayapi.OwningGatewayNameLabel] = i.Proxy.Name + return i } @@ -614,7 +617,7 @@ func TestDeployment(t *testing.T) { if len(tc.extraArgs) > 0 { tc.infra.Proxy.Config.Spec.ExtraArgs = tc.extraArgs } - namespace := cfg.ControllerNamespace + infraNamespace := cfg.ControllerNamespace if tc.gatewayNamespaceMode { deployType := egv1a1.KubernetesDeployModeType(egv1a1.KubernetesDeployModeTypeGatewayNamespace) cfg.EnvoyGateway.Provider = &egv1a1.EnvoyGatewayProvider{ @@ -625,10 +628,10 @@ func TestDeployment(t *testing.T) { }, }, } - namespace = tc.infra.GetProxyInfra().Namespace + infraNamespace = tc.infra.GetProxyInfra().Namespace } - r := NewResourceRender(namespace, cfg.DNSDomain, tc.infra.GetProxyInfra(), cfg.EnvoyGateway) + r := NewResourceRender(infraNamespace, cfg.ControllerNamespace, cfg.DNSDomain, tc.infra.GetProxyInfra(), cfg.EnvoyGateway) dp, err := r.Deployment() require.NoError(t, err) @@ -1057,7 +1060,7 @@ func TestDaemonSet(t *testing.T) { tc.infra.Proxy.Config.Spec.ExtraArgs = tc.extraArgs } - r := NewResourceRender(cfg.ControllerNamespace, cfg.DNSDomain, tc.infra.GetProxyInfra(), cfg.EnvoyGateway) + r := NewResourceRender(cfg.ControllerNamespace, cfg.ControllerNamespace, cfg.DNSDomain, tc.infra.GetProxyInfra(), cfg.EnvoyGateway) ds, err := r.DaemonSet() require.NoError(t, err) @@ -1222,7 +1225,7 @@ func TestService(t *testing.T) { provider.EnvoyService = tc.service } - r := NewResourceRender(cfg.ControllerNamespace, cfg.DNSDomain, tc.infra.GetProxyInfra(), cfg.EnvoyGateway) + r := NewResourceRender(cfg.ControllerNamespace, cfg.ControllerNamespace, cfg.DNSDomain, tc.infra.GetProxyInfra(), cfg.EnvoyGateway) svc, err := r.Service() require.NoError(t, err) @@ -1265,7 +1268,7 @@ func TestConfigMap(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - r := NewResourceRender(cfg.ControllerNamespace, cfg.DNSDomain, tc.infra.GetProxyInfra(), cfg.EnvoyGateway) + r := NewResourceRender(cfg.ControllerNamespace, cfg.ControllerNamespace, cfg.DNSDomain, tc.infra.GetProxyInfra(), cfg.EnvoyGateway) cm, err := r.ConfigMap("") require.NoError(t, err) @@ -1328,7 +1331,7 @@ func TestServiceAccount(t *testing.T) { } ns = tc.infra.GetProxyInfra().Namespace } - r := NewResourceRender(ns, cfg.DNSDomain, tc.infra.GetProxyInfra(), cfg.EnvoyGateway) + r := NewResourceRender(ns, cfg.ControllerNamespace, cfg.DNSDomain, tc.infra.GetProxyInfra(), cfg.EnvoyGateway) sa, err := r.ServiceAccount() require.NoError(t, err) @@ -1452,7 +1455,7 @@ func TestPDB(t *testing.T) { provider.GetEnvoyProxyKubeProvider() - r := NewResourceRender(cfg.ControllerNamespace, cfg.DNSDomain, tc.infra.GetProxyInfra(), cfg.EnvoyGateway) + r := NewResourceRender(cfg.ControllerNamespace, cfg.ControllerNamespace, cfg.DNSDomain, tc.infra.GetProxyInfra(), cfg.EnvoyGateway) pdb, err := r.PodDisruptionBudget() require.NoError(t, err) @@ -1564,7 +1567,7 @@ func TestHorizontalPodAutoscaler(t *testing.T) { } provider.GetEnvoyProxyKubeProvider() - r := NewResourceRender(cfg.ControllerNamespace, cfg.DNSDomain, tc.infra.GetProxyInfra(), cfg.EnvoyGateway) + r := NewResourceRender(cfg.ControllerNamespace, cfg.ControllerNamespace, cfg.DNSDomain, tc.infra.GetProxyInfra(), cfg.EnvoyGateway) hpa, err := r.HorizontalPodAutoscaler() require.NoError(t, err) diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/component-level.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/component-level.yaml index 202550e3ab..fda02c3bd5 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/component-level.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/component-level.yaml @@ -46,10 +46,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -133,10 +130,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/custom.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/custom.yaml index 01dc903d77..10e0d530a3 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/custom.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/custom.yaml @@ -249,10 +249,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -330,10 +327,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default-env.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default-env.yaml index 6f3e23e5b6..c128b84b22 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default-env.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default-env.yaml @@ -248,10 +248,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -329,10 +326,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default.yaml index bfff34fe21..cf2c5ceac4 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/default.yaml @@ -233,10 +233,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -320,10 +317,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/disable-prometheus.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/disable-prometheus.yaml index 16ff0118a1..a6b4881b5d 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/disable-prometheus.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/disable-prometheus.yaml @@ -182,10 +182,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -266,10 +263,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/extension-env.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/extension-env.yaml index d12f8f3f6a..4b82730649 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/extension-env.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/extension-env.yaml @@ -248,10 +248,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -333,10 +330,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml index 17c08ce3d3..ee8578c6bb 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/override-labels-and-annotations.yaml @@ -242,10 +242,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -329,10 +326,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/patch-daemonset.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/patch-daemonset.yaml index 589777f72d..722402065a 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/patch-daemonset.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/patch-daemonset.yaml @@ -233,10 +233,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -320,10 +317,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/shutdown-manager.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/shutdown-manager.yaml index 991f73891e..88b022848c 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/shutdown-manager.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/shutdown-manager.yaml @@ -233,10 +233,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -325,10 +322,7 @@ spec: - name: env_b value: env_b_value - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/volumes.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/volumes.yaml index 34a02349ba..00237afd96 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/volumes.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/volumes.yaml @@ -248,10 +248,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -333,10 +330,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-annotations.yaml index 6756279087..5ecda7a7e0 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-annotations.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-annotations.yaml @@ -238,10 +238,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -325,10 +322,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-concurrency.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-concurrency.yaml index d37b18efe4..9d6236be6d 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-concurrency.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-concurrency.yaml @@ -46,10 +46,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -133,10 +130,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-extra-args.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-extra-args.yaml index 60af1ff399..1e62eb6fd7 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-extra-args.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-extra-args.yaml @@ -235,10 +235,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -322,10 +319,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-image-pull-secrets.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-image-pull-secrets.yaml index 8ff94e418d..a4392c7834 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-image-pull-secrets.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-image-pull-secrets.yaml @@ -233,10 +233,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -320,10 +317,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-name.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-name.yaml index 2f100f2ca4..9d09fccd60 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-name.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-name.yaml @@ -233,10 +233,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -320,10 +317,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-node-selector.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-node-selector.yaml index 479bd3f92c..bdf628b7bc 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-node-selector.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-node-selector.yaml @@ -233,10 +233,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -320,10 +317,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-topology-spread-constraints.yaml b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-topology-spread-constraints.yaml index 7a1ec1637f..d8c7ccef62 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-topology-spread-constraints.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/daemonsets/with-topology-spread-constraints.yaml @@ -233,10 +233,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -320,10 +317,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/bootstrap.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/bootstrap.yaml index 1979ed689e..a41dcc27db 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/bootstrap.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/bootstrap.yaml @@ -49,10 +49,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -136,10 +133,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/component-level.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/component-level.yaml index 588020931d..f9ac32325b 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/component-level.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/component-level.yaml @@ -50,10 +50,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -137,10 +134,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom.yaml index bfe499c13c..2e6f822133 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom.yaml @@ -254,10 +254,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -335,10 +332,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom_with_initcontainers.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom_with_initcontainers.yaml index 0c1203599d..77014daeda 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom_with_initcontainers.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/custom_with_initcontainers.yaml @@ -254,10 +254,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -337,10 +334,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/default-env.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/default-env.yaml index 3452e2a61a..6a1da9c6cf 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/default-env.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/default-env.yaml @@ -253,10 +253,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -334,10 +331,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/default.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/default.yaml index bdb8c2cdec..df745642d6 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/default.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/default.yaml @@ -237,10 +237,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -324,10 +321,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/disable-prometheus.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/disable-prometheus.yaml index 911b4da2c4..cedd6e8212 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/disable-prometheus.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/disable-prometheus.yaml @@ -186,10 +186,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -270,10 +267,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/dual-stack.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/dual-stack.yaml index e9cf766e98..33573899cb 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/dual-stack.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/dual-stack.yaml @@ -238,10 +238,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -325,10 +322,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/extension-env.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/extension-env.yaml index 17a67a3e5f..1aff8bf165 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/extension-env.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/extension-env.yaml @@ -253,10 +253,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -338,10 +335,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/gateway-namespace-mode.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/gateway-namespace-mode.yaml index 731527d592..d659860536 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/gateway-namespace-mode.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/gateway-namespace-mode.yaml @@ -7,7 +7,7 @@ metadata: app.kubernetes.io/managed-by: envoy-gateway app.kubernetes.io/name: envoy gateway.envoyproxy.io/owning-gateway-name: default - gateway.envoyproxy.io/owning-gateway-namespace: default + gateway.envoyproxy.io/owning-gateway-namespace: ns1 name: envoy-default-37a8eec1 namespace: ns1 spec: @@ -19,7 +19,7 @@ spec: app.kubernetes.io/managed-by: envoy-gateway app.kubernetes.io/name: envoy gateway.envoyproxy.io/owning-gateway-name: default - gateway.envoyproxy.io/owning-gateway-namespace: default + gateway.envoyproxy.io/owning-gateway-namespace: ns1 strategy: type: RollingUpdate template: @@ -34,7 +34,7 @@ spec: app.kubernetes.io/managed-by: envoy-gateway app.kubernetes.io/name: envoy gateway.envoyproxy.io/owning-gateway-name: default - gateway.envoyproxy.io/owning-gateway-namespace: default + gateway.envoyproxy.io/owning-gateway-namespace: ns1 spec: containers: - args: @@ -263,7 +263,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: @@ -350,7 +350,7 @@ spec: valueFrom: fieldRef: apiVersion: v1 - fieldPath: metadata.labels['topology.kubernetes.io/zone'] + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - name: ENVOY_POD_NAME valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/ipv6.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/ipv6.yaml index 6ac2eb10f3..0a046d5df7 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/ipv6.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/ipv6.yaml @@ -238,10 +238,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -325,10 +322,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml index 1800214497..1c3f54d508 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/override-labels-and-annotations.yaml @@ -246,10 +246,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -333,10 +330,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/patch-deployment.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/patch-deployment.yaml index 710b667be1..cfe1948be4 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/patch-deployment.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/patch-deployment.yaml @@ -237,10 +237,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -324,10 +321,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/shutdown-manager.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/shutdown-manager.yaml index 455d77c8c8..ec10548c6b 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/shutdown-manager.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/shutdown-manager.yaml @@ -237,10 +237,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -329,10 +326,7 @@ spec: - name: env_b value: env_b_value - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/volumes.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/volumes.yaml index 853165e1db..065c9b05d1 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/volumes.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/volumes.yaml @@ -253,10 +253,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -338,10 +335,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-annotations.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-annotations.yaml index 0a2db99917..af2d9fd1fe 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-annotations.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-annotations.yaml @@ -242,10 +242,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -329,10 +326,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-concurrency.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-concurrency.yaml index fe0bfbc431..f6843521f0 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-concurrency.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-concurrency.yaml @@ -50,10 +50,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -137,10 +134,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-empty-memory-limits.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-empty-memory-limits.yaml index 8fc16b444b..d2c318eaff 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-empty-memory-limits.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-empty-memory-limits.yaml @@ -237,10 +237,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -323,10 +320,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-extra-args.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-extra-args.yaml index d41cf41ad9..3702e2f507 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-extra-args.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-extra-args.yaml @@ -239,10 +239,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -326,10 +323,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-image-pull-secrets.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-image-pull-secrets.yaml index cf8b0b6e92..6cd89fee15 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-image-pull-secrets.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-image-pull-secrets.yaml @@ -237,10 +237,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -324,10 +321,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-name.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-name.yaml index 92266c3f0f..d27361e588 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-name.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-name.yaml @@ -237,10 +237,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -324,10 +321,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-node-selector.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-node-selector.yaml index 7283f66ec1..cd429fb8cc 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-node-selector.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-node-selector.yaml @@ -237,10 +237,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -324,10 +321,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-topology-spread-constraints.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-topology-spread-constraints.yaml index 805c97b906..5b0b60b6ca 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-topology-spread-constraints.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/deployments/with-topology-spread-constraints.yaml @@ -237,10 +237,7 @@ spec: - envoy env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: @@ -324,10 +321,7 @@ spec: - envoy-gateway env: - name: ENVOY_GATEWAY_NAMESPACE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.namespace + value: envoy-gateway-system - name: ENVOY_SERVICE_ZONE valueFrom: fieldRef: diff --git a/internal/infrastructure/kubernetes/proxy/testdata/serviceaccount/gateway-namespace-mode.yaml b/internal/infrastructure/kubernetes/proxy/testdata/serviceaccount/gateway-namespace-mode.yaml index ac3b47bb81..393ada177c 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/serviceaccount/gateway-namespace-mode.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/serviceaccount/gateway-namespace-mode.yaml @@ -7,6 +7,6 @@ metadata: app.kubernetes.io/managed-by: envoy-gateway app.kubernetes.io/name: envoy gateway.envoyproxy.io/owning-gateway-name: default - gateway.envoyproxy.io/owning-gateway-namespace: default + gateway.envoyproxy.io/owning-gateway-namespace: ns1 name: envoy-default-37a8eec1 namespace: ns1 diff --git a/internal/infrastructure/kubernetes/proxy_configmap_test.go b/internal/infrastructure/kubernetes/proxy_configmap_test.go index 7b6c3362e1..ed7343dbe7 100644 --- a/internal/infrastructure/kubernetes/proxy_configmap_test.go +++ b/internal/infrastructure/kubernetes/proxy_configmap_test.go @@ -112,7 +112,7 @@ func TestCreateOrUpdateProxyConfigMap(t *testing.T) { Build() } kube := NewInfra(cli, cfg) - r := proxy.NewResourceRender(kube.Namespace, kube.DNSDomain, infra.GetProxyInfra(), kube.EnvoyGateway) + r := proxy.NewResourceRender(kube.ControllerNamespace, cfg.ControllerNamespace, kube.DNSDomain, infra.GetProxyInfra(), kube.EnvoyGateway) err := kube.createOrUpdateConfigMap(context.Background(), r) require.NoError(t, err) actual := &corev1.ConfigMap{ @@ -170,10 +170,10 @@ func TestDeleteConfigProxyMap(t *testing.T) { infra.Proxy.GetProxyMetadata().Labels[gatewayapi.OwningGatewayNamespaceLabel] = "default" infra.Proxy.GetProxyMetadata().Labels[gatewayapi.OwningGatewayNameLabel] = infra.Proxy.Name - r := proxy.NewResourceRender(kube.Namespace, kube.DNSDomain, infra.GetProxyInfra(), kube.EnvoyGateway) + r := proxy.NewResourceRender(kube.ControllerNamespace, cfg.ControllerNamespace, kube.DNSDomain, infra.GetProxyInfra(), kube.EnvoyGateway) cm := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Namespace: kube.Namespace, + Namespace: kube.ControllerNamespace, Name: r.Name(), }, } diff --git a/internal/infrastructure/kubernetes/proxy_daemonset_test.go b/internal/infrastructure/kubernetes/proxy_daemonset_test.go index 80671d9c03..0217d871ff 100644 --- a/internal/infrastructure/kubernetes/proxy_daemonset_test.go +++ b/internal/infrastructure/kubernetes/proxy_daemonset_test.go @@ -67,7 +67,7 @@ func TestCreateOrUpdateProxyDaemonSet(t *testing.T) { }, } - r := proxy.NewResourceRender(cfg.ControllerNamespace, cfg.DNSDomain, infra.GetProxyInfra(), cfg.EnvoyGateway) + r := proxy.NewResourceRender(cfg.ControllerNamespace, cfg.ControllerNamespace, cfg.DNSDomain, infra.GetProxyInfra(), cfg.EnvoyGateway) ds, err := r.DaemonSet() require.NoError(t, err) @@ -246,7 +246,7 @@ func TestCreateOrUpdateProxyDaemonSet(t *testing.T) { } kube := NewInfra(cli, cfg) - r := proxy.NewResourceRender(kube.Namespace, kube.DNSDomain, tc.in.GetProxyInfra(), cfg.EnvoyGateway) + r := proxy.NewResourceRender(kube.ControllerNamespace, cfg.ControllerNamespace, kube.DNSDomain, tc.in.GetProxyInfra(), cfg.EnvoyGateway) err := kube.createOrUpdateDaemonSet(context.Background(), r) if tc.wantErr { require.Error(t, err) @@ -256,7 +256,7 @@ func TestCreateOrUpdateProxyDaemonSet(t *testing.T) { actual := &appsv1.DaemonSet{ ObjectMeta: metav1.ObjectMeta{ - Namespace: kube.Namespace, + Namespace: kube.ControllerNamespace, Name: proxy.ExpectedResourceHashedName(tc.in.Proxy.Name), }, } diff --git a/internal/infrastructure/kubernetes/proxy_deployment_test.go b/internal/infrastructure/kubernetes/proxy_deployment_test.go index c695a3e7b1..efe72d4c2e 100644 --- a/internal/infrastructure/kubernetes/proxy_deployment_test.go +++ b/internal/infrastructure/kubernetes/proxy_deployment_test.go @@ -60,7 +60,7 @@ func TestCreateOrUpdateProxyDeployment(t *testing.T) { infra.Proxy.GetProxyMetadata().Labels[gatewayapi.OwningGatewayNamespaceLabel] = "default" infra.Proxy.GetProxyMetadata().Labels[gatewayapi.OwningGatewayNameLabel] = infra.Proxy.Name - r := proxy.NewResourceRender(cfg.ControllerNamespace, cfg.DNSDomain, infra.GetProxyInfra(), cfg.EnvoyGateway) + r := proxy.NewResourceRender(cfg.ControllerNamespace, cfg.ControllerNamespace, cfg.DNSDomain, infra.GetProxyInfra(), cfg.EnvoyGateway) deploy, err := r.Deployment() require.NoError(t, err) @@ -239,7 +239,7 @@ func TestCreateOrUpdateProxyDeployment(t *testing.T) { } kube := NewInfra(cli, cfg) - r := proxy.NewResourceRender(kube.Namespace, kube.DNSDomain, tc.in.GetProxyInfra(), cfg.EnvoyGateway) + r := proxy.NewResourceRender(kube.ControllerNamespace, cfg.ControllerNamespace, kube.DNSDomain, tc.in.GetProxyInfra(), cfg.EnvoyGateway) err := kube.createOrUpdateDeployment(context.Background(), r) if tc.wantErr { require.Error(t, err) @@ -249,7 +249,7 @@ func TestCreateOrUpdateProxyDeployment(t *testing.T) { actual := &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Namespace: kube.Namespace, + Namespace: kube.ControllerNamespace, Name: proxy.ExpectedResourceHashedName(tc.in.Proxy.Name), }, } @@ -285,13 +285,13 @@ func TestDeleteProxyDeployment(t *testing.T) { infra := ir.NewInfra() infra.Proxy.GetProxyMetadata().Labels[gatewayapi.OwningGatewayNamespaceLabel] = "default" infra.Proxy.GetProxyMetadata().Labels[gatewayapi.OwningGatewayNameLabel] = infra.Proxy.Name - r := proxy.NewResourceRender(kube.Namespace, kube.DNSDomain, infra.GetProxyInfra(), kube.EnvoyGateway) + r := proxy.NewResourceRender(kube.ControllerNamespace, cfg.ControllerNamespace, kube.DNSDomain, infra.GetProxyInfra(), kube.EnvoyGateway) err := kube.createOrUpdateDeployment(context.Background(), r) require.NoError(t, err) deployment := &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Namespace: kube.Namespace, + Namespace: kube.ControllerNamespace, Name: r.Name(), }, } diff --git a/internal/infrastructure/kubernetes/proxy_infra.go b/internal/infrastructure/kubernetes/proxy_infra.go index f911e5273e..5250489878 100644 --- a/internal/infrastructure/kubernetes/proxy_infra.go +++ b/internal/infrastructure/kubernetes/proxy_infra.go @@ -23,8 +23,8 @@ func (i *Infra) CreateOrUpdateProxyInfra(ctx context.Context, infra *ir.Infra) e return errors.New("infra proxy ir is nil") } - ns := i.GetResourceNamespace(infra) - r := proxy.NewResourceRender(ns, i.DNSDomain, infra.GetProxyInfra(), i.EnvoyGateway) + envoyNamespace := i.GetResourceNamespace(infra) + r := proxy.NewResourceRender(envoyNamespace, i.ControllerNamespace, i.DNSDomain, infra.GetProxyInfra(), i.EnvoyGateway) return i.createOrUpdate(ctx, r) } @@ -34,8 +34,8 @@ func (i *Infra) DeleteProxyInfra(ctx context.Context, infra *ir.Infra) error { return errors.New("infra ir is nil") } - ns := i.GetResourceNamespace(infra) - r := proxy.NewResourceRender(ns, i.DNSDomain, infra.GetProxyInfra(), i.EnvoyGateway) + envoyNamespace := i.GetResourceNamespace(infra) + r := proxy.NewResourceRender(envoyNamespace, i.ControllerNamespace, i.DNSDomain, infra.GetProxyInfra(), i.EnvoyGateway) return i.delete(ctx, r) } @@ -43,5 +43,5 @@ func (i *Infra) GetResourceNamespace(infra *ir.Infra) string { if i.EnvoyGateway.GatewayNamespaceMode() { return infra.Proxy.Namespace } - return i.Namespace + return i.ControllerNamespace } diff --git a/internal/infrastructure/kubernetes/proxy_infra_test.go b/internal/infrastructure/kubernetes/proxy_infra_test.go index 5363b4e805..36eec17056 100644 --- a/internal/infrastructure/kubernetes/proxy_infra_test.go +++ b/internal/infrastructure/kubernetes/proxy_infra_test.go @@ -157,7 +157,7 @@ func TestCreateProxyInfra(t *testing.T) { // Verify all resources were created via the fake kube client. sa := &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ - Namespace: kube.Namespace, + Namespace: kube.ControllerNamespace, Name: proxy.ExpectedResourceHashedName(tc.in.Proxy.Name), }, } @@ -165,7 +165,7 @@ func TestCreateProxyInfra(t *testing.T) { cm := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Namespace: kube.Namespace, + Namespace: kube.ControllerNamespace, Name: proxy.ExpectedResourceHashedName(tc.in.Proxy.Name), }, } @@ -173,7 +173,7 @@ func TestCreateProxyInfra(t *testing.T) { deploy := &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Namespace: kube.Namespace, + Namespace: kube.ControllerNamespace, Name: proxy.ExpectedResourceHashedName(tc.in.Proxy.Name), }, } @@ -181,7 +181,7 @@ func TestCreateProxyInfra(t *testing.T) { svc := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ - Namespace: kube.Namespace, + Namespace: kube.ControllerNamespace, Name: proxy.ExpectedResourceHashedName(tc.in.Proxy.Name), }, } diff --git a/internal/infrastructure/kubernetes/proxy_service_test.go b/internal/infrastructure/kubernetes/proxy_service_test.go index dab16d5b98..296115d4b3 100644 --- a/internal/infrastructure/kubernetes/proxy_service_test.go +++ b/internal/infrastructure/kubernetes/proxy_service_test.go @@ -32,7 +32,7 @@ func TestDeleteProxyService(t *testing.T) { infra.Proxy.GetProxyMetadata().Labels[gatewayapi.OwningGatewayNamespaceLabel] = "default" infra.Proxy.GetProxyMetadata().Labels[gatewayapi.OwningGatewayNameLabel] = infra.Proxy.Name - r := proxy.NewResourceRender(kube.Namespace, kube.DNSDomain, infra.GetProxyInfra(), kube.EnvoyGateway) + r := proxy.NewResourceRender(kube.ControllerNamespace, kube.ControllerNamespace, kube.DNSDomain, infra.GetProxyInfra(), kube.EnvoyGateway) err := kube.createOrUpdateService(context.Background(), r) require.NoError(t, err) diff --git a/internal/infrastructure/kubernetes/proxy_serviceaccount_test.go b/internal/infrastructure/kubernetes/proxy_serviceaccount_test.go index c31a121140..06eb5ef6e7 100644 --- a/internal/infrastructure/kubernetes/proxy_serviceaccount_test.go +++ b/internal/infrastructure/kubernetes/proxy_serviceaccount_test.go @@ -188,13 +188,13 @@ func TestCreateOrUpdateProxyServiceAccount(t *testing.T) { kube := NewInfra(cli, cfg) - r := proxy.NewResourceRender(kube.Namespace, kube.DNSDomain, tc.in.GetProxyInfra(), cfg.EnvoyGateway) + r := proxy.NewResourceRender(kube.ControllerNamespace, cfg.ControllerNamespace, kube.DNSDomain, tc.in.GetProxyInfra(), cfg.EnvoyGateway) err = kube.createOrUpdateServiceAccount(context.Background(), r) require.NoError(t, err) actual := &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ - Namespace: kube.Namespace, + Namespace: kube.ControllerNamespace, Name: proxy.ExpectedResourceHashedName(tc.in.Proxy.Name), }, } @@ -221,7 +221,7 @@ func TestDeleteProxyServiceAccount(t *testing.T) { infra := ir.NewInfra() infra.Proxy.GetProxyMetadata().Labels[gatewayapi.OwningGatewayNamespaceLabel] = "default" infra.Proxy.GetProxyMetadata().Labels[gatewayapi.OwningGatewayNameLabel] = infra.Proxy.Name - r := proxy.NewResourceRender(kube.Namespace, kube.DNSDomain, infra.GetProxyInfra(), kube.EnvoyGateway) + r := proxy.NewResourceRender(kube.ControllerNamespace, kube.ControllerNamespace, kube.DNSDomain, infra.GetProxyInfra(), kube.EnvoyGateway) err := kube.createOrUpdateServiceAccount(context.Background(), r) require.NoError(t, err) diff --git a/internal/infrastructure/kubernetes/ratelimit_deployment_test.go b/internal/infrastructure/kubernetes/ratelimit_deployment_test.go index 93dae1d6d6..ca8193968a 100644 --- a/internal/infrastructure/kubernetes/ratelimit_deployment_test.go +++ b/internal/infrastructure/kubernetes/ratelimit_deployment_test.go @@ -82,13 +82,13 @@ func TestCreateOrUpdateRateLimitDeployment(t *testing.T) { kube := NewInfra(cli, cfg) kube.EnvoyGateway.RateLimit = cfg.EnvoyGateway.RateLimit - r := ratelimit.NewResourceRender(kube.Namespace, kube.EnvoyGateway, ownerReferenceUID) + r := ratelimit.NewResourceRender(kube.ControllerNamespace, kube.EnvoyGateway, ownerReferenceUID) err := kube.createOrUpdateDeployment(context.Background(), r) require.NoError(t, err) actual := &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Namespace: kube.Namespace, + Namespace: kube.ControllerNamespace, Name: ratelimit.InfraName, }, } @@ -123,7 +123,7 @@ func TestDeleteRateLimitDeployment(t *testing.T) { t.Run(tc.name, func(t *testing.T) { kube := newTestInfra(t) kube.EnvoyGateway.RateLimit = rl - r := ratelimit.NewResourceRender(kube.Namespace, kube.EnvoyGateway, nil) + r := ratelimit.NewResourceRender(kube.ControllerNamespace, kube.EnvoyGateway, nil) err := kube.createOrUpdateDeployment(context.Background(), r) require.NoError(t, err) diff --git a/internal/infrastructure/kubernetes/ratelimit_infra.go b/internal/infrastructure/kubernetes/ratelimit_infra.go index d4f5707538..160fa5bd5b 100644 --- a/internal/infrastructure/kubernetes/ratelimit_infra.go +++ b/internal/infrastructure/kubernetes/ratelimit_infra.go @@ -17,14 +17,14 @@ import ( // CreateOrUpdateRateLimitInfra creates the managed kube rate limit infra, if it doesn't exist. func (i *Infra) CreateOrUpdateRateLimitInfra(ctx context.Context) error { - if err := ratelimit.Validate(ctx, i.Client.Client, i.EnvoyGateway, i.Namespace); err != nil { + if err := ratelimit.Validate(ctx, i.Client.Client, i.EnvoyGateway, i.ControllerNamespace); err != nil { return err } // Create ratelimit infra requires the uid of owner reference. ownerReferenceUID := make(map[string]types.UID) key := types.NamespacedName{ - Namespace: i.Namespace, + Namespace: i.ControllerNamespace, Name: "envoy-gateway", } @@ -46,13 +46,13 @@ func (i *Infra) CreateOrUpdateRateLimitInfra(ctx context.Context) error { } ownerReferenceUID[ratelimit.ResourceKindServiceAccount] = serviceAccountUID - r := ratelimit.NewResourceRender(i.Namespace, i.EnvoyGateway, ownerReferenceUID) + r := ratelimit.NewResourceRender(i.ControllerNamespace, i.EnvoyGateway, ownerReferenceUID) return i.createOrUpdate(ctx, r) } // DeleteRateLimitInfra removes the managed kube infra, if it doesn't exist. func (i *Infra) DeleteRateLimitInfra(ctx context.Context) error { // Delete ratelimit infra do not require the uid of owner reference. - r := ratelimit.NewResourceRender(i.Namespace, i.EnvoyGateway, nil) + r := ratelimit.NewResourceRender(i.ControllerNamespace, i.EnvoyGateway, nil) return i.delete(ctx, r) } diff --git a/internal/infrastructure/kubernetes/ratelimit_infra_test.go b/internal/infrastructure/kubernetes/ratelimit_infra_test.go index 1b4976ac36..3fe8c6d7df 100644 --- a/internal/infrastructure/kubernetes/ratelimit_infra_test.go +++ b/internal/infrastructure/kubernetes/ratelimit_infra_test.go @@ -135,11 +135,11 @@ func TestCreateRateLimitInfra(t *testing.T) { for _, ref := range tc.ownerReferences { switch ref { case ratelimit.ResourceKindService: - createEnvoyGatewayService(t, kube.Client.Client, kube.Namespace) + createEnvoyGatewayService(t, kube.Client.Client, kube.ControllerNamespace) case ratelimit.ResourceKindDeployment: - createEnvoyGatewayDeployment(t, kube.Client.Client, kube.Namespace) + createEnvoyGatewayDeployment(t, kube.Client.Client, kube.ControllerNamespace) case ratelimit.ResourceKindServiceAccount: - createEnvoyGatewayServiceAccount(t, kube.Client.Client, kube.Namespace) + createEnvoyGatewayServiceAccount(t, kube.Client.Client, kube.ControllerNamespace) } } @@ -154,7 +154,7 @@ func TestCreateRateLimitInfra(t *testing.T) { // Verify all resources were created via the fake kube client. sa := &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ - Namespace: kube.Namespace, + Namespace: kube.ControllerNamespace, Name: ratelimit.InfraName, }, } @@ -162,7 +162,7 @@ func TestCreateRateLimitInfra(t *testing.T) { deploy := &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Namespace: kube.Namespace, + Namespace: kube.ControllerNamespace, Name: ratelimit.InfraName, }, } @@ -170,7 +170,7 @@ func TestCreateRateLimitInfra(t *testing.T) { svc := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ - Namespace: kube.Namespace, + Namespace: kube.ControllerNamespace, Name: ratelimit.InfraName, }, } diff --git a/internal/infrastructure/kubernetes/ratelimit_service_test.go b/internal/infrastructure/kubernetes/ratelimit_service_test.go index db6578e2a3..59e4c23dc1 100644 --- a/internal/infrastructure/kubernetes/ratelimit_service_test.go +++ b/internal/infrastructure/kubernetes/ratelimit_service_test.go @@ -38,7 +38,7 @@ func TestDeleteRateLimitService(t *testing.T) { kube := newTestInfra(t) kube.EnvoyGateway.RateLimit = rl - r := ratelimit.NewResourceRender(kube.Namespace, kube.EnvoyGateway, nil) + r := ratelimit.NewResourceRender(kube.ControllerNamespace, kube.EnvoyGateway, nil) err := kube.createOrUpdateService(context.Background(), r) require.NoError(t, err) diff --git a/internal/infrastructure/kubernetes/ratelimit_serviceaccount_test.go b/internal/infrastructure/kubernetes/ratelimit_serviceaccount_test.go index e0f13dbcbc..ecf366eeff 100644 --- a/internal/infrastructure/kubernetes/ratelimit_serviceaccount_test.go +++ b/internal/infrastructure/kubernetes/ratelimit_serviceaccount_test.go @@ -114,14 +114,14 @@ func TestCreateOrUpdateRateLimitServiceAccount(t *testing.T) { ownerReferenceUID := map[string]types.UID{ ratelimit.ResourceKindServiceAccount: "foo.bar", } - r := ratelimit.NewResourceRender(kube.Namespace, kube.EnvoyGateway, ownerReferenceUID) + r := ratelimit.NewResourceRender(kube.ControllerNamespace, kube.EnvoyGateway, ownerReferenceUID) err = kube.createOrUpdateServiceAccount(context.Background(), r) require.NoError(t, err) actual := &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ - Namespace: kube.Namespace, + Namespace: kube.ControllerNamespace, Name: ratelimit.InfraName, }, } @@ -158,7 +158,7 @@ func TestDeleteRateLimitServiceAccount(t *testing.T) { kube.EnvoyGateway.RateLimit = rl - r := ratelimit.NewResourceRender(kube.Namespace, kube.EnvoyGateway, nil) + r := ratelimit.NewResourceRender(kube.ControllerNamespace, kube.EnvoyGateway, nil) err := kube.createOrUpdateServiceAccount(context.Background(), r) require.NoError(t, err) From 9593b871fd2dd72205d4805c1c2679e4e8d7cf1e Mon Sep 17 00:00:00 2001 From: zirain Date: Fri, 9 May 2025 23:49:54 +0800 Subject: [PATCH 39/66] e2e: GatewayNamespace mode (#5961) * enable gateway-namespace-mode e2e Signed-off-by: zirain * fix ProxyMetrics Signed-off-by: zirain * fix and skip some tests Signed-off-by: zirain * enable MetricCompressorTest Signed-off-by: zirain * fix upgrade test Signed-off-by: zirain Signed-off-by: Arko Dasgupta --- .github/workflows/build_and_test.yaml | 8 +++++ test/e2e/e2e_test.go | 26 +++++++++++++- test/e2e/testdata/metric-compressor.yaml | 43 ++++++++++++---------- test/e2e/testdata/metric.yaml | 19 ---------- test/e2e/tests/accesslog.go | 6 ++-- test/e2e/tests/envoy_shutdown.go | 10 ++++-- test/e2e/tests/envoyproxy.go | 46 ++++++++++++------------ test/e2e/tests/envoyproxy_hpa.go | 10 +++--- test/e2e/tests/metric.go | 32 +++++------------ test/e2e/tests/ratelimit.go | 12 ------- test/e2e/tests/utils.go | 24 ++++++++++--- test/e2e/upgrade/eg_upgrade_test.go | 18 +++++++--- 12 files changed, 139 insertions(+), 115 deletions(-) diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml index 200e7d5718..580d2d7413 100644 --- a/.github/workflows/build_and_test.yaml +++ b/.github/workflows/build_and_test.yaml @@ -127,13 +127,20 @@ jobs: target: - version: v1.30.10 ipFamily: ipv4 + profile: default - version: v1.31.6 ipFamily: ipv4 + profile: default - version: v1.32.3 ipFamily: ipv6 # only run ipv6 test on this version to save time + profile: default # TODO: this's IPv4 first, need a way to test IPv6 first. - version: v1.33.0 ipFamily: dual # only run dual test on latest version to save time + profile: default + - version: v1.33.0 + ipFamily: dual # only run dual test on latest version to save time + profile: gateway-namespace-mode steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: ./tools/github-actions/setup-deps @@ -155,6 +162,7 @@ jobs: KIND_NODE_TAG: ${{ matrix.target.version }} IMAGE_PULL_POLICY: IfNotPresent IP_FAMILY: ${{ matrix.target.ipFamily }} + KUBE_DEPLOY_PROFILE: ${{ matrix.target.profile }} E2E_TIMEOUT: 1h NUM_WORKERS: 2 run: make e2e diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 12a4973808..c7de5de62e 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -55,6 +55,30 @@ func TestE2E(t *testing.T) { if tests.IPFamily == "ipv6" { skipTests = append(skipTests, tests.DynamicResolverBackendTest.ShortName, + tests.RateLimitCIDRMatchTest.ShortName, + tests.RateLimitMultipleListenersTest.ShortName, + tests.RateLimitGlobalSharedCidrMatchTest.ShortName, + ) + } + + // TODO: make these tests work in GatewayNamespaceMode + if tests.IsGatewayNamespaceMode() { + skipTests = append(skipTests, + tests.HTTPWasmTest.ShortName, + tests.OCIWasmTest.ShortName, + tests.ZoneAwareRoutingTest.ShortName, + + // Skip RateLimit tests because they are not supported in GatewayNamespaceMode + tests.RateLimitCIDRMatchTest.ShortName, + tests.RateLimitHeaderMatchTest.ShortName, + tests.GlobalRateLimitHeaderInvertMatchTest.ShortName, + tests.RateLimitHeadersDisabled.ShortName, + tests.RateLimitBasedJwtClaimsTest.ShortName, + tests.RateLimitMultipleListenersTest.ShortName, + tests.RateLimitHeadersAndCIDRMatchTest.ShortName, + tests.UsageRateLimitTest.ShortName, + tests.RateLimitGlobalSharedCidrMatchTest.ShortName, + tests.RateLimitGlobalSharedGatewayHeaderMatchTest.ShortName, ) } @@ -68,7 +92,7 @@ func TestE2E(t *testing.T) { RunTest: *flags.RunTest, // SupportedFeatures cannot be empty, so we set it to SupportGateway // All e2e tests should leave Features empty. - SupportedFeatures: sets.New[features.FeatureName](features.SupportGateway), + SupportedFeatures: sets.New(features.SupportGateway), SkipTests: skipTests, AllowCRDsMismatch: *flags.AllowCRDsMismatch, }) diff --git a/test/e2e/testdata/metric-compressor.yaml b/test/e2e/testdata/metric-compressor.yaml index e3bc342c55..17199be3e6 100644 --- a/test/e2e/testdata/metric-compressor.yaml +++ b/test/e2e/testdata/metric-compressor.yaml @@ -25,6 +25,18 @@ metadata: namespace: gateway-conformance-infra spec: ipFamily: IPv4 + provider: + type: Kubernetes + kubernetes: + envoyService: + patch: + value: + spec: + ports: + - name: metrics + protocol: TCP + port: 19001 + targetPort: 19001 telemetry: metrics: prometheus: @@ -94,31 +106,24 @@ metadata: namespace: gateway-conformance-infra spec: ipFamily: IPv4 + provider: + type: Kubernetes + kubernetes: + envoyService: + patch: + value: + spec: + ports: + - name: metrics + protocol: TCP + port: 19001 + targetPort: 19001 telemetry: metrics: prometheus: compression: type: Brotli --- -apiVersion: v1 -kind: Service -metadata: - name: brotli-gtw-metrics - namespace: envoy-gateway-system -spec: - selector: - app.kubernetes.io/component: proxy - app.kubernetes.io/managed-by: envoy-gateway - app.kubernetes.io/name: envoy - gateway.envoyproxy.io/owning-gateway-name: brotli-gtw - gateway.envoyproxy.io/owning-gateway-namespace: gateway-conformance-infra - ports: - - name: metrics - protocol: TCP - port: 19001 - targetPort: 19001 - type: LoadBalancer ---- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: diff --git a/test/e2e/testdata/metric.yaml b/test/e2e/testdata/metric.yaml index b65c1d3afc..7173c902ec 100644 --- a/test/e2e/testdata/metric.yaml +++ b/test/e2e/testdata/metric.yaml @@ -61,25 +61,6 @@ spec: --- apiVersion: v1 kind: Service -metadata: - name: same-namespace-gw-metrics - namespace: envoy-gateway-system -spec: - selector: - app.kubernetes.io/component: proxy - app.kubernetes.io/managed-by: envoy-gateway - app.kubernetes.io/name: envoy - gateway.envoyproxy.io/owning-gateway-name: same-namespace - gateway.envoyproxy.io/owning-gateway-namespace: gateway-conformance-infra - ports: - - name: metrics - protocol: TCP - port: 19001 - targetPort: 19001 - type: LoadBalancer ---- -apiVersion: v1 -kind: Service metadata: name: otel-collecot-prometheus namespace: monitoring diff --git a/test/e2e/tests/accesslog.go b/test/e2e/tests/accesslog.go index 5df762868d..ea07349fd7 100644 --- a/test/e2e/tests/accesslog.go +++ b/test/e2e/tests/accesslog.go @@ -9,6 +9,7 @@ package tests import ( "context" + "fmt" "testing" "time" @@ -29,9 +30,10 @@ var FileAccessLogTest = suite.ConformanceTest{ Description: "Make sure file access log is working", Manifests: []string{"testdata/accesslog-file.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + gatwayNS := GetGatewayResourceNamespace() labels := map[string]string{ - "job": "envoy-gateway-system/envoy", - "namespace": "envoy-gateway-system", + "job": fmt.Sprintf("%s/envoy", gatwayNS), + "namespace": gatwayNS, "container": "envoy", } match := "test-annotation-value" diff --git a/test/e2e/tests/envoy_shutdown.go b/test/e2e/tests/envoy_shutdown.go index 14a223ddef..646154c3db 100644 --- a/test/e2e/tests/envoy_shutdown.go +++ b/test/e2e/tests/envoy_shutdown.go @@ -51,8 +51,7 @@ var EnvoyShutdownTest = suite.ConformanceTest{ t.Errorf("Failed to get proxy deployment") } - // Wait for the grpc ext auth service pod to be ready - WaitForPods(t, suite.Client, "envoy-gateway-system", map[string]string{"gateway.envoyproxy.io/owning-gateway-name": name}, corev1.PodRunning, PodReady) + WaitForPods(t, suite.Client, dp.Namespace, map[string]string{"gateway.envoyproxy.io/owning-gateway-name": name}, corev1.PodRunning, PodReady) // wait for route to be programmed on envoy expectedResponse := http.ExpectedResponse{ @@ -103,8 +102,13 @@ func getDeploymentForGateway(namespace, name string, c client.Client) (*appsv1.D } ctx := context.Background() + gwNs := "envoy-gateway-system" + if IsGatewayNamespaceMode() { + // use the namespace of Gateway resource + gwNs = namespace + } listOpts := []client.ListOption{ - client.InNamespace("envoy-gateway-system"), + client.InNamespace(gwNs), client.MatchingLabelsSelector{Selector: labels.SelectorFromSet(dpLabels)}, } diff --git a/test/e2e/tests/envoyproxy.go b/test/e2e/tests/envoyproxy.go index b8980bd096..d366d05ab2 100644 --- a/test/e2e/tests/envoyproxy.go +++ b/test/e2e/tests/envoyproxy.go @@ -36,11 +36,13 @@ var EnvoyProxyCustomNameTest = suite.ConformanceTest{ Description: "Test running Envoy with custom name", Manifests: []string{"testdata/envoyproxy-custom-name.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + gatwayNS := GetGatewayResourceNamespace() + t.Run("Deployment", func(t *testing.T) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "deploy-route", Namespace: ns} gwNN := types.NamespacedName{Name: "deploy-custom-name", Namespace: ns} - OkResp := http.ExpectedResponse{ + okResp := http.ExpectedResponse{ Request: http.Request{ Path: "/deploy", }, @@ -51,17 +53,17 @@ var EnvoyProxyCustomNameTest = suite.ConformanceTest{ } // Make sure there's deployment for the gateway - err := checkEnvoyProxyDeployment(t, suite, gwNN, fmt.Sprintf("envoy-%s-%s", gwNN.Namespace, gwNN.Name)) + err := checkEnvoyProxyDeployment(t, suite, gwNN, gatwayNS, fmt.Sprintf("envoy-%s-%s", gwNN.Namespace, gwNN.Name)) if err != nil { t.Fatalf("Failed to check EnvoyProxy deployment: %v", err) } - err = checkEnvoyProxyService(t, suite, gwNN, fmt.Sprintf("envoy-%s-%s", gwNN.Namespace, gwNN.Name)) + err = checkEnvoyProxyService(t, suite, gwNN, gatwayNS, fmt.Sprintf("envoy-%s-%s", gwNN.Namespace, gwNN.Name)) if err != nil { t.Fatalf("Failed to check EnvoyProxy service: %v", err) } gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) // Send a request to a valid path and expect a successful response - http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, OkResp) + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, okResp) // Update the Gateway to use a custom name gw := &gwapiv1.Gateway{} @@ -81,17 +83,17 @@ var EnvoyProxyCustomNameTest = suite.ConformanceTest{ t.Fatalf("Failed to update Gateway: %v", err) } - err = checkEnvoyProxyDeployment(t, suite, gwNN, "deploy-custom-name") + err = checkEnvoyProxyDeployment(t, suite, gwNN, gatwayNS, "deploy-custom-name") if err != nil { t.Fatalf("Failed to delete Gateway: %v", err) } - err = checkEnvoyProxyService(t, suite, gwNN, "deploy-custom-name") + err = checkEnvoyProxyService(t, suite, gwNN, gatwayNS, "deploy-custom-name") if err != nil { t.Fatalf("Failed to check EnvoyProxy service: %v", err) } gwAddr = kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) // Send a request to a valid path and expect a successful response - http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, OkResp) + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, okResp) // Rollback the Gateway to without custom name gw = &gwapiv1.Gateway{} @@ -106,17 +108,17 @@ var EnvoyProxyCustomNameTest = suite.ConformanceTest{ } // Make sure there's deployment for the gateway - err = checkEnvoyProxyDeployment(t, suite, gwNN, fmt.Sprintf("envoy-%s-%s", gwNN.Namespace, gwNN.Name)) + err = checkEnvoyProxyDeployment(t, suite, gwNN, gatwayNS, fmt.Sprintf("envoy-%s-%s", gwNN.Namespace, gwNN.Name)) if err != nil { t.Fatalf("Failed to check EnvoyProxy deployment: %v", err) } - err = checkEnvoyProxyService(t, suite, gwNN, fmt.Sprintf("envoy-%s-%s", gwNN.Namespace, gwNN.Name)) + err = checkEnvoyProxyService(t, suite, gwNN, gatwayNS, fmt.Sprintf("envoy-%s-%s", gwNN.Namespace, gwNN.Name)) if err != nil { t.Fatalf("Failed to check EnvoyProxy service: %v", err) } gwAddr = kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) // Send a request to a valid path and expect a successful response - http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, OkResp) + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, okResp) }) t.Run("DaemonSet", func(t *testing.T) { @@ -134,11 +136,11 @@ var EnvoyProxyCustomNameTest = suite.ConformanceTest{ } // Make sure there's DaemonSet for the gateway - err := checkEnvoyProxyDaemonSet(t, suite, gwNN, fmt.Sprintf("envoy-%s-%s", gwNN.Namespace, gwNN.Name)) + err := checkEnvoyProxyDaemonSet(t, suite, gwNN, gatwayNS, fmt.Sprintf("envoy-%s-%s", gwNN.Namespace, gwNN.Name)) if err != nil { t.Fatalf("Failed to check EnvoyProxy deployment: %v", err) } - err = checkEnvoyProxyService(t, suite, gwNN, fmt.Sprintf("envoy-%s-%s", gwNN.Namespace, gwNN.Name)) + err = checkEnvoyProxyService(t, suite, gwNN, gatwayNS, fmt.Sprintf("envoy-%s-%s", gwNN.Namespace, gwNN.Name)) if err != nil { t.Fatalf("Failed to check EnvoyProxy service: %v", err) } @@ -164,11 +166,11 @@ var EnvoyProxyCustomNameTest = suite.ConformanceTest{ t.Fatalf("Failed to update Gateway: %v", err) } - err = checkEnvoyProxyDaemonSet(t, suite, gwNN, "ds-custom-name") + err = checkEnvoyProxyDaemonSet(t, suite, gwNN, gatwayNS, "ds-custom-name") if err != nil { t.Fatalf("Failed to delete Gateway: %v", err) } - err = checkEnvoyProxyService(t, suite, gwNN, "ds-custom-name") + err = checkEnvoyProxyService(t, suite, gwNN, gatwayNS, "ds-custom-name") if err != nil { t.Fatalf("Failed to check EnvoyProxy service: %v", err) } @@ -195,11 +197,11 @@ var EnvoyProxyCustomNameTest = suite.ConformanceTest{ } // Make sure there's DaemonSet for the gateway - err = checkEnvoyProxyDaemonSet(t, suite, gwNN, fmt.Sprintf("envoy-%s-%s", gwNN.Namespace, gwNN.Name)) + err = checkEnvoyProxyDaemonSet(t, suite, gwNN, gatwayNS, fmt.Sprintf("envoy-%s-%s", gwNN.Namespace, gwNN.Name)) if err != nil { t.Fatalf("Failed to check EnvoyProxy deployment: %v", err) } - err = checkEnvoyProxyService(t, suite, gwNN, fmt.Sprintf("envoy-%s-%s", gwNN.Namespace, gwNN.Name)) + err = checkEnvoyProxyService(t, suite, gwNN, gatwayNS, fmt.Sprintf("envoy-%s-%s", gwNN.Namespace, gwNN.Name)) if err != nil { t.Fatalf("Failed to check EnvoyProxy service: %v", err) } @@ -210,12 +212,12 @@ var EnvoyProxyCustomNameTest = suite.ConformanceTest{ }, } -func checkEnvoyProxyDeployment(t *testing.T, suite *suite.ConformanceTestSuite, gwNN types.NamespacedName, exceptName string) error { +func checkEnvoyProxyDeployment(t *testing.T, suite *suite.ConformanceTestSuite, gwNN types.NamespacedName, exceptNs, exceptName string) error { // Make sure there's deployment for the gateway return wait.PollUntilContextTimeout(context.TODO(), time.Second, suite.TimeoutConfig.CreateTimeout, true, func(ctx context.Context) (bool, error) { deploys := &appsv1.DeploymentList{} err := suite.Client.List(ctx, deploys, &client.ListOptions{ - Namespace: "envoy-gateway-system", + Namespace: exceptNs, LabelSelector: labels.SelectorFromSet(map[string]string{ "app.kubernetes.io/managed-by": "envoy-gateway", "app.kubernetes.io/name": "envoy", @@ -246,12 +248,12 @@ func checkEnvoyProxyDeployment(t *testing.T, suite *suite.ConformanceTestSuite, }) } -func checkEnvoyProxyService(t *testing.T, suite *suite.ConformanceTestSuite, gwNN types.NamespacedName, exceptName string) error { +func checkEnvoyProxyService(t *testing.T, suite *suite.ConformanceTestSuite, gwNN types.NamespacedName, exceptNs, exceptName string) error { // Make sure there's deployment for the gateway return wait.PollUntilContextTimeout(context.TODO(), time.Second, suite.TimeoutConfig.CreateTimeout, true, func(ctx context.Context) (bool, error) { svcList := &corev1.ServiceList{} err := suite.Client.List(ctx, svcList, &client.ListOptions{ - Namespace: "envoy-gateway-system", + Namespace: exceptNs, LabelSelector: labels.SelectorFromSet(map[string]string{ "app.kubernetes.io/managed-by": "envoy-gateway", "app.kubernetes.io/name": "envoy", @@ -278,12 +280,12 @@ func checkEnvoyProxyService(t *testing.T, suite *suite.ConformanceTestSuite, gwN }) } -func checkEnvoyProxyDaemonSet(t *testing.T, suite *suite.ConformanceTestSuite, gwNN types.NamespacedName, exceptName string) error { +func checkEnvoyProxyDaemonSet(t *testing.T, suite *suite.ConformanceTestSuite, gwNN types.NamespacedName, exceptNs, exceptName string) error { // Make sure there's deployment for the gateway return wait.PollUntilContextTimeout(context.TODO(), time.Second, suite.TimeoutConfig.CreateTimeout, true, func(ctx context.Context) (bool, error) { dsList := &appsv1.DaemonSetList{} err := suite.Client.List(ctx, dsList, &client.ListOptions{ - Namespace: "envoy-gateway-system", + Namespace: exceptNs, LabelSelector: labels.SelectorFromSet(map[string]string{ "app.kubernetes.io/managed-by": "envoy-gateway", "app.kubernetes.io/name": "envoy", diff --git a/test/e2e/tests/envoyproxy_hpa.go b/test/e2e/tests/envoyproxy_hpa.go index 4e4a830e0f..8901a407ac 100644 --- a/test/e2e/tests/envoyproxy_hpa.go +++ b/test/e2e/tests/envoyproxy_hpa.go @@ -42,9 +42,11 @@ var EnvoyProxyHPATest = suite.ConformanceTest{ Namespace: ns, } + expectedNs := GetGatewayResourceNamespace() + // Make sure there's a deployment/HPA for the gateway - ExpectEnvoyProxyDeploymentCount(t, suite, gwNN, 1) - ExpectEnvoyProxyHPACount(t, suite, gwNN, 1) + ExpectEnvoyProxyDeploymentCount(t, suite, gwNN, expectedNs, 1) + ExpectEnvoyProxyHPACount(t, suite, gwNN, expectedNs, 1) // Send a request to a valid path and expect a successful response http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, OkResp) @@ -61,8 +63,8 @@ var EnvoyProxyHPATest = suite.ConformanceTest{ t.Fatalf("Failed to update Gateway: %v", err) } - ExpectEnvoyProxyDeploymentCount(t, suite, gwNN, 1) - ExpectEnvoyProxyHPACount(t, suite, gwNN, 0) + ExpectEnvoyProxyDeploymentCount(t, suite, gwNN, expectedNs, 1) + ExpectEnvoyProxyHPACount(t, suite, gwNN, expectedNs, 0) }) }, } diff --git a/test/e2e/tests/metric.go b/test/e2e/tests/metric.go index ec1eb2019a..f61ead25ab 100644 --- a/test/e2e/tests/metric.go +++ b/test/e2e/tests/metric.go @@ -57,13 +57,16 @@ var MetricTest = suite.ConformanceTest{ // let's check the metric if err := wait.PollUntilContextTimeout(context.TODO(), time.Second, time.Minute, true, func(_ context.Context) (done bool, err error) { - if err := ScrapeMetrics(t, suite.Client, types.NamespacedName{ - Namespace: "envoy-gateway-system", - Name: "same-namespace-gw-metrics", - }, 19001, "/stats/prometheus"); err != nil { + pql := fmt.Sprintf(`envoy_cluster_default_total_match_count{app_kubernetes_io_component="proxy", app_kubernetes_io_managed_by="envoy-gateway", app_kubernetes_io_name="envoy", envoy_cluster_name="xds_cluster", gateway_envoyproxy_io_owning_gateway_name="%s"}`, "same-namespace") + v, err := prometheus.QueryPrometheus(suite.Client, pql) + if err != nil { tlog.Logf(t, "failed to get metric: %v", err) return false, nil } + if v != nil { + tlog.Logf(t, "got expected value: %v", v) + return true, nil + } return true, nil }); err != nil { t.Errorf("failed to scrape metrics: %v", err) @@ -175,25 +178,8 @@ func runMetricCompressorTest(t *testing.T, suite *suite.ConformanceTestSuite, ns } httputils.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse) - // make sure compression work as expected - statsNN := types.NamespacedName{Namespace: "envoy-gateway-system", Name: fmt.Sprintf("%s-gtw-metrics", compressor)} - var statsHost string - if err := wait.PollUntilContextTimeout(context.TODO(), time.Second, time.Minute, true, func(_ context.Context) (done bool, err error) { - addr, err := ServiceHost(suite.Client, statsNN, 19001) - if err != nil { - tlog.Logf(t, "failed to get service host %s: %v", statsNN, err) - return false, nil - } - if addr != "" { - statsHost = addr - return true, nil - } - return false, nil - }); err != nil { - t.Errorf("failed to get service host %s: %v", statsNN, err) - return - } - + // stats exposed at port 19001 + statsHost := strings.Replace(gwAddr, ":80", ":19001", 1) statsAddr := fmt.Sprintf("http://%s/stats/prometheus", statsHost) tlog.Logf(t, "check stats from %s", statsAddr) diff --git a/test/e2e/tests/ratelimit.go b/test/e2e/tests/ratelimit.go index a425a23a33..da8cff9fdc 100644 --- a/test/e2e/tests/ratelimit.go +++ b/test/e2e/tests/ratelimit.go @@ -48,10 +48,6 @@ var RateLimitCIDRMatchTest = suite.ConformanceTest{ Description: "Limit all requests that match CIDR", Manifests: []string{"testdata/ratelimit-cidr-match.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { - if IPFamily == "ipv6" { - t.Skip("Skipping test as IP_FAMILY is IPv6") - } - t.Run("block all ips", func(t *testing.T) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "cidr-ratelimit", Namespace: ns} @@ -514,10 +510,6 @@ var RateLimitMultipleListenersTest = suite.ConformanceTest{ Description: "Limit requests on multiple listeners", Manifests: []string{"testdata/ratelimit-multiple-listeners.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { - if IPFamily == "ipv6" { - t.Skip("Skipping test as IP_FAMILY is IPv6") - } - t.Run("block all ips on listener 80 and 8080", func(t *testing.T) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "cidr-ratelimit", Namespace: ns} @@ -755,10 +747,6 @@ var RateLimitGlobalSharedCidrMatchTest = suite.ConformanceTest{ Description: "Limit all requests that match CIDR across multiple routes with a shared rate limit", Manifests: []string{"testdata/ratelimit-global-shared-cidr-match.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { - if IPFamily == "ipv6" { - t.Skip("Skipping test as IP_FAMILY is IPv6") - } - t.Run("block all ips with shared rate limit across routes with different paths", func(t *testing.T) { ns := "gateway-conformance-infra" route1NN := types.NamespacedName{Name: "cidr-ratelimit-1", Namespace: ns} diff --git a/test/e2e/tests/utils.go b/test/e2e/tests/utils.go index 824e75218b..5700f82af9 100644 --- a/test/e2e/tests/utils.go +++ b/test/e2e/tests/utils.go @@ -46,7 +46,10 @@ import ( tb "github.com/envoyproxy/gateway/internal/troubleshoot" ) -var IPFamily = os.Getenv("IP_FAMILY") +var ( + IPFamily = os.Getenv("IP_FAMILY") + DeployProfile = os.Getenv("KUBE_DEPLOY_PROFILE") +) const defaultServiceStartupTimeout = 5 * time.Minute @@ -645,11 +648,11 @@ func ContentEncoding(compressorType egv1a1.CompressorType) string { return encoding } -func ExpectEnvoyProxyDeploymentCount(t *testing.T, suite *suite.ConformanceTestSuite, gwNN types.NamespacedName, expectedCount int) { +func ExpectEnvoyProxyDeploymentCount(t *testing.T, suite *suite.ConformanceTestSuite, gwNN types.NamespacedName, expectedNs string, expectedCount int) { err := wait.PollUntilContextTimeout(context.TODO(), time.Second, suite.TimeoutConfig.DeleteTimeout, true, func(ctx context.Context) (bool, error) { deploys := &appsv1.DeploymentList{} err := suite.Client.List(ctx, deploys, &client.ListOptions{ - Namespace: "envoy-gateway-system", + Namespace: expectedNs, LabelSelector: labels.SelectorFromSet(map[string]string{ "app.kubernetes.io/managed-by": "envoy-gateway", "app.kubernetes.io/name": "envoy", @@ -668,11 +671,11 @@ func ExpectEnvoyProxyDeploymentCount(t *testing.T, suite *suite.ConformanceTestS } } -func ExpectEnvoyProxyHPACount(t *testing.T, suite *suite.ConformanceTestSuite, gwNN types.NamespacedName, expectedCount int) { +func ExpectEnvoyProxyHPACount(t *testing.T, suite *suite.ConformanceTestSuite, gwNN types.NamespacedName, expectedNs string, expectedCount int) { err := wait.PollUntilContextTimeout(context.TODO(), time.Second, suite.TimeoutConfig.DeleteTimeout, true, func(ctx context.Context) (bool, error) { hpa := &autoscalingv2.HorizontalPodAutoscalerList{} err := suite.Client.List(ctx, hpa, &client.ListOptions{ - Namespace: "envoy-gateway-system", + Namespace: expectedNs, LabelSelector: labels.SelectorFromSet(map[string]string{ "gateway.envoyproxy.io/owning-gateway-name": gwNN.Name, "gateway.envoyproxy.io/owning-gateway-namespace": gwNN.Namespace, @@ -688,3 +691,14 @@ func ExpectEnvoyProxyHPACount(t *testing.T, suite *suite.ConformanceTestSuite, g t.Fatalf("Failed to check HPA count(%d) for the Gateway: %v", expectedCount, err) } } + +func IsGatewayNamespaceMode() bool { + return DeployProfile == "gateway-namespace-mode" +} + +func GetGatewayResourceNamespace() string { + if IsGatewayNamespaceMode() { + return "gateway-conformance-infra" + } + return "envoy-gateway-system" +} diff --git a/test/e2e/upgrade/eg_upgrade_test.go b/test/e2e/upgrade/eg_upgrade_test.go index 9af99c81c9..509b874fb8 100644 --- a/test/e2e/upgrade/eg_upgrade_test.go +++ b/test/e2e/upgrade/eg_upgrade_test.go @@ -57,17 +57,25 @@ func TestEGUpgrade(t *testing.T) { ManifestFS: []fs.FS{e2e.UpgradeManifests}, RunTest: *flags.RunTest, BaseManifests: "upgrade/manifests.yaml", - SupportedFeatures: sets.New[features.FeatureName](features.SupportGateway), + SupportedFeatures: sets.New(features.SupportGateway), SkipTests: skipTests, }) if err != nil { t.Fatalf("Failed to create test suite: %v", err) } - // upgrade tests should be executed in a specific order - tests.UpgradeTests = []suite.ConformanceTest{ - tests.EnvoyShutdownTest, - tests.EGUpgradeTest, + if tests.IsGatewayNamespaceMode() { + tests.UpgradeTests = []suite.ConformanceTest{ + tests.EnvoyShutdownTest, + // TODO: enable EGUpgradeTest in gateway namespace mode + // tests.EGUpgradeTest, + } + } else { + // upgrade tests should be executed in a specific order + tests.UpgradeTests = []suite.ConformanceTest{ + tests.EnvoyShutdownTest, + tests.EGUpgradeTest, + } } tlog.Logf(t, "Running %d Upgrade tests", len(tests.UpgradeTests)) From 38ef39d604f258dc5914598b5e82b6b124a34fba Mon Sep 17 00:00:00 2001 From: "Huabing (Robin) Zhao" Date: Sat, 10 May 2025 01:09:48 +0800 Subject: [PATCH 40/66] helm: support standard channel (#5958) * support standard channel Signed-off-by: Huabing (Robin) Zhao * add comment Signed-off-by: Huabing (Robin) Zhao --------- Signed-off-by: Huabing (Robin) Zhao Signed-off-by: Arko Dasgupta --- charts/gateway-crds-helm/README.md | 1 + ...yaml => experimental-gatewayapi-crds.yaml} | 2 +- .../templates/standard-gatewayapi-crds.yaml | 10613 ++++++++++ charts/gateway-crds-helm/values.tmpl.yaml | 1 + charts/gateway-crds-helm/values.yaml | 1 + release-notes/current.yaml | 1 + .../latest/install/gateway-crds-helm-api.md | 1 + test/helm/gateway-crds-helm/all.out.yaml | 24 +- .../gateway-api-crds.out.yaml | 24 +- .../gateway-api-experimental-crds.in.yaml | 6 + .../gateway-api-experimental-crds.out.yaml | 17331 ++++++++++++++++ .../gateway-api-standard-crds.in.yaml | 6 + .../gateway-api-standard-crds.out.yaml | 10618 ++++++++++ tools/make/kube.mk | 21 +- 14 files changed, 38618 insertions(+), 32 deletions(-) rename charts/gateway-crds-helm/templates/{gatewayapi-crds.yaml => experimental-gatewayapi-crds.yaml} (99%) create mode 100644 charts/gateway-crds-helm/templates/standard-gatewayapi-crds.yaml create mode 100644 test/helm/gateway-crds-helm/gateway-api-experimental-crds.in.yaml create mode 100644 test/helm/gateway-crds-helm/gateway-api-experimental-crds.out.yaml create mode 100644 test/helm/gateway-crds-helm/gateway-api-standard-crds.in.yaml create mode 100644 test/helm/gateway-crds-helm/gateway-api-standard-crds.out.yaml diff --git a/charts/gateway-crds-helm/README.md b/charts/gateway-crds-helm/README.md index 87d5388c74..54998fa960 100644 --- a/charts/gateway-crds-helm/README.md +++ b/charts/gateway-crds-helm/README.md @@ -33,5 +33,6 @@ To uninstall the chart: | Key | Type | Default | Description | |-----|------|---------|-------------| | crds.envoyGateway.enabled | bool | `false` | | +| crds.gatewayAPI.channel | string | `"experimental"` | | | crds.gatewayAPI.enabled | bool | `false` | | diff --git a/charts/gateway-crds-helm/templates/gatewayapi-crds.yaml b/charts/gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml similarity index 99% rename from charts/gateway-crds-helm/templates/gatewayapi-crds.yaml rename to charts/gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml index 949b95a694..4d4d363e23 100644 --- a/charts/gateway-crds-helm/templates/gatewayapi-crds.yaml +++ b/charts/gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml @@ -1,4 +1,4 @@ -{{- if .Values.crds.gatewayAPI.enabled }} +{{- if and .Values.crds.gatewayAPI.enabled (or (eq .Values.crds.gatewayAPI.channel "experimental") (eq .Values.crds.gatewayAPI.channel "")) }} # Copyright 2025 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/charts/gateway-crds-helm/templates/standard-gatewayapi-crds.yaml b/charts/gateway-crds-helm/templates/standard-gatewayapi-crds.yaml new file mode 100644 index 0000000000..a69b816150 --- /dev/null +++ b/charts/gateway-crds-helm/templates/standard-gatewayapi-crds.yaml @@ -0,0 +1,10613 @@ +{{- if and .Values.crds.gatewayAPI.enabled (eq .Values.crds.gatewayAPI.channel "standard") }} +# Copyright 2025 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Gateway API Standard channel install +# +--- +# +# config/crd/standard/gateway.networking.k8s.io_gatewayclasses.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: standard + creationTimestamp: null + name: gatewayclasses.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: GatewayClass + listKind: GatewayClassList + plural: gatewayclasses + shortNames: + - gc + singular: gatewayclass + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.controllerName + name: Controller + type: string + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.description + name: Description + priority: 1 + type: string + name: v1 + schema: + openAPIV3Schema: + description: |- + GatewayClass describes a class of Gateways available to the user for creating + Gateway resources. + + 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. This recommendation is intended to + limit the blast radius of changes to GatewayClass or associated parameters. + If implementations choose to propagate GatewayClass changes to existing + Gateways, that MUST be clearly documented by the implementation. + + Whenever one or more Gateways are using a GatewayClass, implementations SHOULD + add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the + associated GatewayClass. This ensures that a GatewayClass associated with a + Gateway is not deleted while in use. + + GatewayClass is a Cluster level resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClass. + properties: + controllerName: + description: |- + ControllerName is the name of the controller that is managing Gateways of + this class. The value of this field MUST be a domain prefixed path. + + Example: "example.net/gateway-controller". + + This field is not mutable and cannot be empty. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + description: + description: Description helps describe a GatewayClass with more details. + maxLength: 64 + type: string + parametersRef: + description: |- + ParametersRef is a reference to a resource that contains the configuration + parameters corresponding to the GatewayClass. This is optional if the + controller does not require any additional configuration. + + ParametersRef can reference a standard Kubernetes resource, i.e. ConfigMap, + or an implementation-specific custom resource. The resource can be + cluster-scoped or namespace-scoped. + + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the GatewayClass SHOULD be + rejected with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + + A Gateway for this GatewayClass may provide its own `parametersRef`. When both are specified, + the merging behavior is implementation specific. + It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway. + + Support: Implementation-specific + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. + This field is required when referring to a Namespace-scoped resource and + MUST be unset when referring to a Cluster-scoped resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + required: + - controllerName + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: |- + Status defines the current state of GatewayClass. + + Implementations MUST populate status on all GatewayClass resources which + specify their controller name. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: |- + Conditions is the current status from the controller for + this GatewayClass. + + Controllers should prefer to publish conditions using values + of GatewayClassConditionType for the type of each Condition. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.controllerName + name: Controller + type: string + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.description + name: Description + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: |- + GatewayClass describes a class of Gateways available to the user for creating + Gateway resources. + + 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. This recommendation is intended to + limit the blast radius of changes to GatewayClass or associated parameters. + If implementations choose to propagate GatewayClass changes to existing + Gateways, that MUST be clearly documented by the implementation. + + Whenever one or more Gateways are using a GatewayClass, implementations SHOULD + add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the + associated GatewayClass. This ensures that a GatewayClass associated with a + Gateway is not deleted while in use. + + GatewayClass is a Cluster level resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClass. + properties: + controllerName: + description: |- + ControllerName is the name of the controller that is managing Gateways of + this class. The value of this field MUST be a domain prefixed path. + + Example: "example.net/gateway-controller". + + This field is not mutable and cannot be empty. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + description: + description: Description helps describe a GatewayClass with more details. + maxLength: 64 + type: string + parametersRef: + description: |- + ParametersRef is a reference to a resource that contains the configuration + parameters corresponding to the GatewayClass. This is optional if the + controller does not require any additional configuration. + + ParametersRef can reference a standard Kubernetes resource, i.e. ConfigMap, + or an implementation-specific custom resource. The resource can be + cluster-scoped or namespace-scoped. + + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the GatewayClass SHOULD be + rejected with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + + A Gateway for this GatewayClass may provide its own `parametersRef`. When both are specified, + the merging behavior is implementation specific. + It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway. + + Support: Implementation-specific + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. + This field is required when referring to a Namespace-scoped resource and + MUST be unset when referring to a Cluster-scoped resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + required: + - controllerName + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: |- + Status defines the current state of GatewayClass. + + Implementations MUST populate status on all GatewayClass resources which + specify their controller name. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: |- + Conditions is the current status from the controller for + this GatewayClass. + + Controllers should prefer to publish conditions using values + of GatewayClassConditionType for the type of each Condition. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# +# config/crd/standard/gateway.networking.k8s.io_gateways.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: standard + creationTimestamp: null + name: gateways.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: Gateway + listKind: GatewayList + plural: gateways + shortNames: + - gtw + singular: gateway + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.gatewayClassName + name: Class + type: string + - jsonPath: .status.addresses[*].value + name: Address + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + Gateway represents an instance of a service-traffic handling infrastructure + by binding Listeners to a set of IP addresses. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of Gateway. + properties: + addresses: + description: |- + Addresses requested for this Gateway. This is optional and behavior can + depend on the implementation. If a value is set in the spec and the + requested address is invalid or unavailable, the implementation MUST + indicate this in the associated entry in GatewayStatus.Addresses. + + The Addresses field represents a request for the address(es) on the + "outside of the Gateway", that traffic bound for this Gateway will use. + This could be the IP address or hostname of an external load balancer or + other networking infrastructure, or some other address that traffic will + be sent to. + + If no Addresses are specified, the implementation MAY schedule the + Gateway in an implementation-specific manner, assigning an appropriate + set of Addresses. + + The implementation MUST bind all Listeners to every GatewayAddress that + it assigns to the Gateway and add a corresponding entry in + GatewayStatus.Addresses. + + Support: Extended + items: + description: GatewaySpecAddress describes an address that can be + bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: |- + When a value is unspecified, an implementation SHOULD automatically + assign an address matching the requested type if possible. + + If an implementation does not support an empty value, they MUST set the + "Programmed" condition in status to False with a reason of "AddressNotAssigned". + + Examples: `1.2.3.4`, `128::1`, `my-ip-address`. + maxLength: 253 + type: string + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching + ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + true' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: IPAddress values must be unique + rule: 'self.all(a1, a1.type == ''IPAddress'' ? self.exists_one(a2, + a2.type == a1.type && a2.value == a1.value) : true )' + - message: Hostname values must be unique + rule: 'self.all(a1, a1.type == ''Hostname'' ? self.exists_one(a2, + a2.type == a1.type && a2.value == a1.value) : true )' + gatewayClassName: + description: |- + GatewayClassName used for this Gateway. This is the name of a + GatewayClass resource. + maxLength: 253 + minLength: 1 + type: string + infrastructure: + description: |- + Infrastructure defines infrastructure level attributes about this Gateway instance. + + Support: Extended + properties: + annotations: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Annotations that SHOULD be applied to any resources created in response to this Gateway. + + For implementations creating other Kubernetes objects, this should be the `metadata.annotations` field on resources. + For other implementations, this refers to any relevant (implementation specific) "annotations" concepts. + + An implementation may chose to add additional implementation-specific annotations as they see fit. + + Support: Extended + maxProperties: 8 + type: object + x-kubernetes-validations: + - message: Annotation keys must be in the form of an optional + DNS subdomain prefix followed by a required name segment of + up to 63 characters. + rule: self.all(key, key.matches(r"""^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9]$""")) + - message: If specified, the annotation key's prefix must be a + DNS subdomain not longer than 253 characters in total. + rule: self.all(key, key.split("/")[0].size() < 253) + labels: + additionalProperties: + description: |- + LabelValue is the value of a label in the Gateway API. This is used for validation + of maps such as Gateway infrastructure labels. This matches the Kubernetes + label validation rules: + * must be 63 characters or less (can be empty), + * unless empty, must begin and end with an alphanumeric character ([a-z0-9A-Z]), + * could contain dashes (-), underscores (_), dots (.), and alphanumerics between. + + Valid values include: + + * MyValue + * my.name + * 123-my-value + maxLength: 63 + minLength: 0 + pattern: ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$ + type: string + description: |- + Labels that SHOULD be applied to any resources created in response to this Gateway. + + For implementations creating other Kubernetes objects, this should be the `metadata.labels` field on resources. + For other implementations, this refers to any relevant (implementation specific) "labels" concepts. + + An implementation may chose to add additional implementation-specific labels as they see fit. + + If an implementation maps these labels to Pods, or any other resource that would need to be recreated when labels + change, it SHOULD clearly warn about this behavior in documentation. + + Support: Extended + maxProperties: 8 + type: object + x-kubernetes-validations: + - message: Label keys must be in the form of an optional DNS subdomain + prefix followed by a required name segment of up to 63 characters. + rule: self.all(key, key.matches(r"""^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9]$""")) + - message: If specified, the label key's prefix must be a DNS + subdomain not longer than 253 characters in total. + rule: self.all(key, key.split("/")[0].size() < 253) + parametersRef: + description: |- + ParametersRef is a reference to a resource that contains the configuration + parameters corresponding to the Gateway. This is optional if the + controller does not require any additional configuration. + + This follows the same semantics as GatewayClass's `parametersRef`, but on a per-Gateway basis + + The Gateway's GatewayClass may provide its own `parametersRef`. When both are specified, + the merging behavior is implementation specific. + It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway. + + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the Gateway SHOULD be + rejected with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + + Support: Implementation-specific + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + type: object + listeners: + description: |- + Listeners associated with this Gateway. Listeners define + logical endpoints that are bound on this Gateway's addresses. + At least one Listener MUST be specified. + + ## Distinct Listeners + + Each Listener in a set of Listeners (for example, in a single Gateway) + MUST be _distinct_, in that a traffic flow MUST be able to be assigned to + exactly one listener. (This section uses "set of Listeners" rather than + "Listeners in a single Gateway" because implementations MAY merge configuration + from multiple Gateways onto a single data plane, and these rules _also_ + apply in that case). + + Practically, this means that each listener in a set MUST have a unique + combination of Port, Protocol, and, if supported by the protocol, Hostname. + + Some combinations of port, protocol, and TLS settings are considered + Core support and MUST be supported by implementations based on the objects + they support: + + HTTPRoute + + 1. HTTPRoute, Port: 80, Protocol: HTTP + 2. HTTPRoute, Port: 443, Protocol: HTTPS, TLS Mode: Terminate, TLS keypair provided + + TLSRoute + + 1. TLSRoute, Port: 443, Protocol: TLS, TLS Mode: Passthrough + + "Distinct" Listeners have the following property: + + **The implementation can match inbound requests to a single distinct + Listener**. + + When multiple Listeners share values for fields (for + example, two Listeners with the same Port value), the implementation + can match requests to only one of the Listeners using other + Listener fields. + + When multiple listeners have the same value for the Protocol field, then + each of the Listeners with matching Protocol values MUST have different + values for other fields. + + The set of fields that MUST be different for a Listener differs per protocol. + The following rules define the rules for what fields MUST be considered for + Listeners to be distinct with each protocol currently defined in the + Gateway API spec. + + The set of listeners that all share a protocol value MUST have _different_ + values for _at least one_ of these fields to be distinct: + + * **HTTP, HTTPS, TLS**: Port, Hostname + * **TCP, UDP**: Port + + One **very** important rule to call out involves what happens when an + implementation: + + * Supports TCP protocol Listeners, as well as HTTP, HTTPS, or TLS protocol + Listeners, and + * sees HTTP, HTTPS, or TLS protocols with the same `port` as one with TCP + Protocol. + + In this case all the Listeners that share a port with the + TCP Listener are not distinct and so MUST NOT be accepted. + + If an implementation does not support TCP Protocol Listeners, then the + previous rule does not apply, and the TCP Listeners SHOULD NOT be + accepted. + + Note that the `tls` field is not used for determining if a listener is distinct, because + Listeners that _only_ differ on TLS config will still conflict in all cases. + + ### Listeners that are distinct only by Hostname + + When the Listeners are distinct based only on Hostname, inbound request + hostnames MUST match from the most specific to least specific Hostname + values to choose the correct Listener and its associated set of Routes. + + Exact matches MUST be processed before wildcard matches, and wildcard + matches MUST be processed before fallback (empty Hostname value) + matches. For example, `"foo.example.com"` takes precedence over + `"*.example.com"`, and `"*.example.com"` takes precedence over `""`. + + Additionally, if there are multiple wildcard entries, more specific + wildcard entries must be processed before less specific wildcard entries. + For example, `"*.foo.example.com"` takes precedence over `"*.example.com"`. + + The precise definition here is that the higher the number of dots in the + hostname to the right of the wildcard character, the higher the precedence. + + The wildcard character will match any number of characters _and dots_ to + the left, however, so `"*.example.com"` will match both + `"foo.bar.example.com"` _and_ `"bar.example.com"`. + + ## Handling indistinct Listeners + + If a set of Listeners contains Listeners that are not distinct, then those + Listeners are _Conflicted_, and the implementation MUST set the "Conflicted" + condition in the Listener Status to "True". + + The words "indistinct" and "conflicted" are considered equivalent for the + purpose of this documentation. + + Implementations MAY choose to accept a Gateway with some Conflicted + Listeners only if they only accept the partial Listener set that contains + no Conflicted Listeners. + + Specifically, an implementation MAY accept a partial Listener set subject to + the following rules: + + * The implementation MUST NOT pick one conflicting Listener as the winner. + ALL indistinct Listeners must not be accepted for processing. + * At least one distinct Listener MUST be present, or else the Gateway effectively + contains _no_ Listeners, and must be rejected from processing as a whole. + + The implementation MUST set a "ListenersNotValid" condition on the + Gateway Status when the Gateway contains Conflicted Listeners whether or + not they accept the Gateway. That Condition SHOULD clearly + indicate in the Message which Listeners are conflicted, and which are + Accepted. Additionally, the Listener status for those listeners SHOULD + indicate which Listeners are conflicted and not Accepted. + + ## General Listener behavior + + Note that, for all distinct Listeners, requests SHOULD match at most one Listener. + For example, if Listeners are defined for "foo.example.com" and "*.example.com", a + request to "foo.example.com" SHOULD only be routed using routes attached + to the "foo.example.com" Listener (and not the "*.example.com" Listener). + + This concept is known as "Listener Isolation", and it is an Extended feature + of Gateway API. Implementations that do not support Listener Isolation MUST + clearly document this, and MUST NOT claim support for the + `GatewayHTTPListenerIsolation` feature. + + Implementations that _do_ support Listener Isolation SHOULD claim support + for the Extended `GatewayHTTPListenerIsolation` feature and pass the associated + conformance tests. + + ## Compatible Listeners + + A Gateway's Listeners are considered _compatible_ if: + + 1. They are distinct. + 2. The implementation can serve them in compliance with the Addresses + requirement that all Listeners are available on all assigned + addresses. + + Compatible combinations in Extended support are expected to vary across + implementations. A combination that is compatible for one implementation + may not be compatible for another. + + For example, an implementation that cannot serve both TCP and UDP listeners + on the same address, or cannot mix HTTPS and generic TLS listens on the same port + would not consider those cases compatible, even though they are distinct. + + Implementations MAY merge separate Gateways onto a single set of + Addresses if all Listeners across all Gateways are compatible. + + In a future release the MinItems=1 requirement MAY be dropped. + + Support: Core + items: + description: |- + Listener embodies the concept of a logical endpoint where a Gateway accepts + network connections. + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: |- + AllowedRoutes defines the types of routes that MAY be attached to a + Listener and the trusted namespaces where those Route resources MAY be + present. + + Although a client request may match multiple route rules, only one rule + may ultimately receive the request. Matching precedence MUST be + determined in order of the following criteria: + + * The most specific match as defined by the Route type. + * The oldest Route based on creation timestamp. For example, a Route with + a creation timestamp of "2020-09-08 01:02:03" is given precedence over + a Route with a creation timestamp of "2020-09-08 01:02:04". + * If everything else is equivalent, the Route appearing first in + alphabetical order (namespace/name) should be given precedence. For + example, foo/bar is given precedence over foo/baz. + + All valid rules within a Route attached to this Listener should be + implemented. Invalid Route rules can be ignored (sometimes that will mean + the full Route). If a Route rule transitions from valid to invalid, + support for that Route rule should be dropped to ensure consistency. For + example, even if a filter specified by a Route rule is invalid, the rest + of the rules within that Route should still be supported. + + Support: Core + properties: + kinds: + description: |- + Kinds specifies the groups and kinds of Routes that are allowed to bind + to this Gateway Listener. When unspecified or empty, the kinds of Routes + selected are determined using the Listener protocol. + + A RouteGroupKind MUST correspond to kinds of Routes that are compatible + with the application protocol specified in the Listener's Protocol field. + If an implementation does not support or recognize this resource type, it + MUST set the "ResolvedRefs" condition to False for this Listener with the + "InvalidRouteKinds" reason. + + Support: Core + items: + description: RouteGroupKind indicates the group and kind + of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: |- + Namespaces indicates namespaces from which Routes may be attached to this + Listener. This is restricted to the namespace of this Gateway by default. + + Support: Core + properties: + from: + default: Same + description: |- + From indicates where Routes will be selected for this Gateway. Possible + values are: + + * All: Routes in all namespaces may be used by this Gateway. + * Selector: Routes in namespaces selected by the selector may be used by + this Gateway. + * Same: Only Routes in the same namespace may be used by this Gateway. + + Support: Core + enum: + - All + - Selector + - Same + type: string + selector: + description: |- + Selector must be specified when From is set to "Selector". In that case, + only Routes in Namespaces matching this Selector will be selected by this + Gateway. This field is ignored for other values of "From". + + Support: Core + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + type: object + type: object + hostname: + description: |- + Hostname specifies the virtual hostname to match for protocol types that + define this concept. When unspecified, all hostnames are matched. This + field is ignored for protocols that don't require hostname based + matching. + + Implementations MUST apply Hostname matching appropriately for each of + the following protocols: + + * TLS: The Listener Hostname MUST match the SNI. + * HTTP: The Listener Hostname MUST match the Host header of the request. + * HTTPS: The Listener Hostname SHOULD match both the SNI and Host header. + Note that this does not require the SNI and Host header to be the same. + The semantics of this are described in more detail below. + + To ensure security, Section 11.1 of RFC-6066 emphasizes that server + implementations that rely on SNI hostname matching MUST also verify + hostnames within the application protocol. + + Section 9.1.2 of RFC-7540 provides a mechanism for servers to reject the + reuse of a connection by responding with the HTTP 421 Misdirected Request + status code. This indicates that the origin server has rejected the + request because it appears to have been misdirected. + + To detect misdirected requests, Gateways SHOULD match the authority of + the requests with all the SNI hostname(s) configured across all the + Gateway Listeners on the same port and protocol: + + * If another Listener has an exact match or more specific wildcard entry, + the Gateway SHOULD return a 421. + * If the current Listener (selected by SNI matching during ClientHello) + does not match the Host: + * If another Listener does match the Host the Gateway SHOULD return a + 421. + * If no other Listener matches the Host, the Gateway MUST return a + 404. + + For HTTPRoute and TLSRoute resources, there is an interaction with the + `spec.hostnames` array. When both listener and route specify hostnames, + there MUST be an intersection between the values for a Route to be + accepted. For more information, refer to the Route specific Hostnames + documentation. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + name: + description: |- + Name is the name of the Listener. This name MUST be unique within a + Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: |- + Port is the network port. Multiple listeners may use the + same port, subject to the Listener compatibility rules. + + Support: Core + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: |- + Protocol specifies the network protocol this listener expects to receive. + + Support: Core + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: |- + TLS is the TLS configuration for the Listener. This field is required if + the Protocol field is "HTTPS" or "TLS". It is invalid to set this field + if the Protocol field is "HTTP", "TCP", or "UDP". + + The association of SNIs to Certificate defined in GatewayTLSConfig is + defined based on the Hostname field for this listener. + + The GatewayClass MUST use the longest matching SNI out of all + available certificates for any TLS handshake. + + Support: Core + properties: + certificateRefs: + description: |- + CertificateRefs contains a series of references to Kubernetes objects that + contains TLS certificates and private keys. These certificates are used to + establish a TLS handshake for requests that match the hostname of the + associated listener. + + A single CertificateRef to a Kubernetes Secret has "Core" support. + Implementations MAY choose to support attaching multiple certificates to + a Listener, but this behavior is implementation-specific. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + + This field is required to have at least one element when the mode is set + to "Terminate" (default) and is optional otherwise. + + CertificateRefs can reference to standard Kubernetes resources, i.e. + Secret, or implementation-specific custom resources. + + Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls + + Support: Implementation-specific (More than one reference or other resource types) + items: + description: |- + SecretObjectReference identifies an API object including its namespace, + defaulting to Secret. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example + "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + mode: + default: Terminate + description: |- + Mode defines the TLS behavior for the TLS session initiated by the client. + There are two possible modes: + + - Terminate: The TLS session between the downstream client and the + Gateway is terminated at the Gateway. This mode requires certificates + to be specified in some way, such as populating the certificateRefs + field. + - Passthrough: The TLS session is NOT terminated by the Gateway. This + implies that the Gateway can't decipher the TLS stream except for + the ClientHello message of the TLS protocol. The certificateRefs field + is ignored in this mode. + + Support: Core + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Options are a list of key/value pairs to enable extended TLS + configuration for each implementation. For example, configuring the + minimum TLS version or supported cipher suites. + + A set of common keys MAY be defined by the API in the future. To avoid + any ambiguity, implementation-specific definitions MUST use + domain-prefixed names, such as `example.com/my-custom-option`. + Un-prefixed names are reserved for key names defined by Gateway API. + + Support: Implementation-specific + maxProperties: 16 + type: object + type: object + x-kubernetes-validations: + - message: certificateRefs or options must be specified when + mode is Terminate + rule: 'self.mode == ''Terminate'' ? size(self.certificateRefs) + > 0 || size(self.options) > 0 : true' + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: tls must not be specified for protocols ['HTTP', 'TCP', + 'UDP'] + rule: 'self.all(l, l.protocol in [''HTTP'', ''TCP'', ''UDP''] ? + !has(l.tls) : true)' + - message: tls mode must be Terminate for protocol HTTPS + rule: 'self.all(l, (l.protocol == ''HTTPS'' && has(l.tls)) ? (l.tls.mode + == '''' || l.tls.mode == ''Terminate'') : true)' + - message: hostname must not be specified for protocols ['TCP', 'UDP'] + rule: 'self.all(l, l.protocol in [''TCP'', ''UDP''] ? (!has(l.hostname) + || l.hostname == '''') : true)' + - message: Listener name must be unique within the Gateway + rule: self.all(l1, self.exists_one(l2, l1.name == l2.name)) + - message: Combination of port, protocol and hostname must be unique + for each listener + rule: 'self.all(l1, self.exists_one(l2, l1.port == l2.port && l1.protocol + == l2.protocol && (has(l1.hostname) && has(l2.hostname) ? l1.hostname + == l2.hostname : !has(l1.hostname) && !has(l2.hostname))))' + required: + - gatewayClassName + - listeners + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: Status defines the current state of Gateway. + properties: + addresses: + description: |- + Addresses lists the network addresses that have been bound to the + Gateway. + + This list may differ from the addresses provided in the spec under some + conditions: + + * no addresses are specified, all addresses are dynamically assigned + * a combination of specified and dynamic addresses are assigned + * a specified address was unusable (e.g. already in use) + items: + description: GatewayStatusAddress describes a network address that + is bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: |- + Value of the address. The validity of the values will depend + on the type and support by the controller. + + Examples: `1.2.3.4`, `128::1`, `my-ip-address`. + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching + ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + true' + maxItems: 16 + type: array + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: |- + Conditions describe the current conditions of the Gateway. + + Implementations should prefer to express Gateway conditions + using the `GatewayConditionType` and `GatewayConditionReason` + constants so that operators and tools can converge on a common + vocabulary to describe Gateway state. + + Known condition types are: + + * "Accepted" + * "Programmed" + * "Ready" + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port + defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: |- + AttachedRoutes represents the total number of Routes that have been + successfully attached to this Listener. + + Successful attachment of a Route to a Listener is based solely on the + combination of the AllowedRoutes field on the corresponding Listener + and the Route's ParentRefs field. A Route is successfully attached to + a Listener when it is selected by the Listener's AllowedRoutes field + AND the Route has a valid ParentRef selecting the whole Gateway + resource or a specific Listener as a parent resource (more detail on + attachment semantics can be found in the documentation on the various + Route kinds ParentRefs fields). Listener or Route status does not impact + successful attachment, i.e. the AttachedRoutes field count MUST be set + for Listeners with condition Accepted: false and MUST count successfully + attached Routes that may themselves have Accepted: false conditions. + + Uses for this field include troubleshooting Route attachment and + measuring blast radius/impact of changes to a Listener. + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this + listener. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status + corresponds to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + supportedKinds: + description: |- + SupportedKinds is the list indicating the Kinds supported by this + listener. This MUST represent the kinds an implementation supports for + that Listener configuration. + + If kinds are specified in Spec that are not supported, they MUST NOT + appear in this list and an implementation MUST set the "ResolvedRefs" + condition to "False" with the "InvalidRouteKinds" reason. If both valid + and invalid Route kinds are specified, the implementation MUST + reference the valid Route kinds that have been specified. + items: + description: RouteGroupKind indicates the group and kind of + a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.gatewayClassName + name: Class + type: string + - jsonPath: .status.addresses[*].value + name: Address + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: |- + Gateway represents an instance of a service-traffic handling infrastructure + by binding Listeners to a set of IP addresses. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of Gateway. + properties: + addresses: + description: |- + Addresses requested for this Gateway. This is optional and behavior can + depend on the implementation. If a value is set in the spec and the + requested address is invalid or unavailable, the implementation MUST + indicate this in the associated entry in GatewayStatus.Addresses. + + The Addresses field represents a request for the address(es) on the + "outside of the Gateway", that traffic bound for this Gateway will use. + This could be the IP address or hostname of an external load balancer or + other networking infrastructure, or some other address that traffic will + be sent to. + + If no Addresses are specified, the implementation MAY schedule the + Gateway in an implementation-specific manner, assigning an appropriate + set of Addresses. + + The implementation MUST bind all Listeners to every GatewayAddress that + it assigns to the Gateway and add a corresponding entry in + GatewayStatus.Addresses. + + Support: Extended + items: + description: GatewaySpecAddress describes an address that can be + bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: |- + When a value is unspecified, an implementation SHOULD automatically + assign an address matching the requested type if possible. + + If an implementation does not support an empty value, they MUST set the + "Programmed" condition in status to False with a reason of "AddressNotAssigned". + + Examples: `1.2.3.4`, `128::1`, `my-ip-address`. + maxLength: 253 + type: string + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching + ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + true' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: IPAddress values must be unique + rule: 'self.all(a1, a1.type == ''IPAddress'' ? self.exists_one(a2, + a2.type == a1.type && a2.value == a1.value) : true )' + - message: Hostname values must be unique + rule: 'self.all(a1, a1.type == ''Hostname'' ? self.exists_one(a2, + a2.type == a1.type && a2.value == a1.value) : true )' + gatewayClassName: + description: |- + GatewayClassName used for this Gateway. This is the name of a + GatewayClass resource. + maxLength: 253 + minLength: 1 + type: string + infrastructure: + description: |- + Infrastructure defines infrastructure level attributes about this Gateway instance. + + Support: Extended + properties: + annotations: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Annotations that SHOULD be applied to any resources created in response to this Gateway. + + For implementations creating other Kubernetes objects, this should be the `metadata.annotations` field on resources. + For other implementations, this refers to any relevant (implementation specific) "annotations" concepts. + + An implementation may chose to add additional implementation-specific annotations as they see fit. + + Support: Extended + maxProperties: 8 + type: object + x-kubernetes-validations: + - message: Annotation keys must be in the form of an optional + DNS subdomain prefix followed by a required name segment of + up to 63 characters. + rule: self.all(key, key.matches(r"""^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9]$""")) + - message: If specified, the annotation key's prefix must be a + DNS subdomain not longer than 253 characters in total. + rule: self.all(key, key.split("/")[0].size() < 253) + labels: + additionalProperties: + description: |- + LabelValue is the value of a label in the Gateway API. This is used for validation + of maps such as Gateway infrastructure labels. This matches the Kubernetes + label validation rules: + * must be 63 characters or less (can be empty), + * unless empty, must begin and end with an alphanumeric character ([a-z0-9A-Z]), + * could contain dashes (-), underscores (_), dots (.), and alphanumerics between. + + Valid values include: + + * MyValue + * my.name + * 123-my-value + maxLength: 63 + minLength: 0 + pattern: ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$ + type: string + description: |- + Labels that SHOULD be applied to any resources created in response to this Gateway. + + For implementations creating other Kubernetes objects, this should be the `metadata.labels` field on resources. + For other implementations, this refers to any relevant (implementation specific) "labels" concepts. + + An implementation may chose to add additional implementation-specific labels as they see fit. + + If an implementation maps these labels to Pods, or any other resource that would need to be recreated when labels + change, it SHOULD clearly warn about this behavior in documentation. + + Support: Extended + maxProperties: 8 + type: object + x-kubernetes-validations: + - message: Label keys must be in the form of an optional DNS subdomain + prefix followed by a required name segment of up to 63 characters. + rule: self.all(key, key.matches(r"""^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9]$""")) + - message: If specified, the label key's prefix must be a DNS + subdomain not longer than 253 characters in total. + rule: self.all(key, key.split("/")[0].size() < 253) + parametersRef: + description: |- + ParametersRef is a reference to a resource that contains the configuration + parameters corresponding to the Gateway. This is optional if the + controller does not require any additional configuration. + + This follows the same semantics as GatewayClass's `parametersRef`, but on a per-Gateway basis + + The Gateway's GatewayClass may provide its own `parametersRef`. When both are specified, + the merging behavior is implementation specific. + It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway. + + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the Gateway SHOULD be + rejected with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + + Support: Implementation-specific + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + type: object + listeners: + description: |- + Listeners associated with this Gateway. Listeners define + logical endpoints that are bound on this Gateway's addresses. + At least one Listener MUST be specified. + + ## Distinct Listeners + + Each Listener in a set of Listeners (for example, in a single Gateway) + MUST be _distinct_, in that a traffic flow MUST be able to be assigned to + exactly one listener. (This section uses "set of Listeners" rather than + "Listeners in a single Gateway" because implementations MAY merge configuration + from multiple Gateways onto a single data plane, and these rules _also_ + apply in that case). + + Practically, this means that each listener in a set MUST have a unique + combination of Port, Protocol, and, if supported by the protocol, Hostname. + + Some combinations of port, protocol, and TLS settings are considered + Core support and MUST be supported by implementations based on the objects + they support: + + HTTPRoute + + 1. HTTPRoute, Port: 80, Protocol: HTTP + 2. HTTPRoute, Port: 443, Protocol: HTTPS, TLS Mode: Terminate, TLS keypair provided + + TLSRoute + + 1. TLSRoute, Port: 443, Protocol: TLS, TLS Mode: Passthrough + + "Distinct" Listeners have the following property: + + **The implementation can match inbound requests to a single distinct + Listener**. + + When multiple Listeners share values for fields (for + example, two Listeners with the same Port value), the implementation + can match requests to only one of the Listeners using other + Listener fields. + + When multiple listeners have the same value for the Protocol field, then + each of the Listeners with matching Protocol values MUST have different + values for other fields. + + The set of fields that MUST be different for a Listener differs per protocol. + The following rules define the rules for what fields MUST be considered for + Listeners to be distinct with each protocol currently defined in the + Gateway API spec. + + The set of listeners that all share a protocol value MUST have _different_ + values for _at least one_ of these fields to be distinct: + + * **HTTP, HTTPS, TLS**: Port, Hostname + * **TCP, UDP**: Port + + One **very** important rule to call out involves what happens when an + implementation: + + * Supports TCP protocol Listeners, as well as HTTP, HTTPS, or TLS protocol + Listeners, and + * sees HTTP, HTTPS, or TLS protocols with the same `port` as one with TCP + Protocol. + + In this case all the Listeners that share a port with the + TCP Listener are not distinct and so MUST NOT be accepted. + + If an implementation does not support TCP Protocol Listeners, then the + previous rule does not apply, and the TCP Listeners SHOULD NOT be + accepted. + + Note that the `tls` field is not used for determining if a listener is distinct, because + Listeners that _only_ differ on TLS config will still conflict in all cases. + + ### Listeners that are distinct only by Hostname + + When the Listeners are distinct based only on Hostname, inbound request + hostnames MUST match from the most specific to least specific Hostname + values to choose the correct Listener and its associated set of Routes. + + Exact matches MUST be processed before wildcard matches, and wildcard + matches MUST be processed before fallback (empty Hostname value) + matches. For example, `"foo.example.com"` takes precedence over + `"*.example.com"`, and `"*.example.com"` takes precedence over `""`. + + Additionally, if there are multiple wildcard entries, more specific + wildcard entries must be processed before less specific wildcard entries. + For example, `"*.foo.example.com"` takes precedence over `"*.example.com"`. + + The precise definition here is that the higher the number of dots in the + hostname to the right of the wildcard character, the higher the precedence. + + The wildcard character will match any number of characters _and dots_ to + the left, however, so `"*.example.com"` will match both + `"foo.bar.example.com"` _and_ `"bar.example.com"`. + + ## Handling indistinct Listeners + + If a set of Listeners contains Listeners that are not distinct, then those + Listeners are _Conflicted_, and the implementation MUST set the "Conflicted" + condition in the Listener Status to "True". + + The words "indistinct" and "conflicted" are considered equivalent for the + purpose of this documentation. + + Implementations MAY choose to accept a Gateway with some Conflicted + Listeners only if they only accept the partial Listener set that contains + no Conflicted Listeners. + + Specifically, an implementation MAY accept a partial Listener set subject to + the following rules: + + * The implementation MUST NOT pick one conflicting Listener as the winner. + ALL indistinct Listeners must not be accepted for processing. + * At least one distinct Listener MUST be present, or else the Gateway effectively + contains _no_ Listeners, and must be rejected from processing as a whole. + + The implementation MUST set a "ListenersNotValid" condition on the + Gateway Status when the Gateway contains Conflicted Listeners whether or + not they accept the Gateway. That Condition SHOULD clearly + indicate in the Message which Listeners are conflicted, and which are + Accepted. Additionally, the Listener status for those listeners SHOULD + indicate which Listeners are conflicted and not Accepted. + + ## General Listener behavior + + Note that, for all distinct Listeners, requests SHOULD match at most one Listener. + For example, if Listeners are defined for "foo.example.com" and "*.example.com", a + request to "foo.example.com" SHOULD only be routed using routes attached + to the "foo.example.com" Listener (and not the "*.example.com" Listener). + + This concept is known as "Listener Isolation", and it is an Extended feature + of Gateway API. Implementations that do not support Listener Isolation MUST + clearly document this, and MUST NOT claim support for the + `GatewayHTTPListenerIsolation` feature. + + Implementations that _do_ support Listener Isolation SHOULD claim support + for the Extended `GatewayHTTPListenerIsolation` feature and pass the associated + conformance tests. + + ## Compatible Listeners + + A Gateway's Listeners are considered _compatible_ if: + + 1. They are distinct. + 2. The implementation can serve them in compliance with the Addresses + requirement that all Listeners are available on all assigned + addresses. + + Compatible combinations in Extended support are expected to vary across + implementations. A combination that is compatible for one implementation + may not be compatible for another. + + For example, an implementation that cannot serve both TCP and UDP listeners + on the same address, or cannot mix HTTPS and generic TLS listens on the same port + would not consider those cases compatible, even though they are distinct. + + Implementations MAY merge separate Gateways onto a single set of + Addresses if all Listeners across all Gateways are compatible. + + In a future release the MinItems=1 requirement MAY be dropped. + + Support: Core + items: + description: |- + Listener embodies the concept of a logical endpoint where a Gateway accepts + network connections. + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: |- + AllowedRoutes defines the types of routes that MAY be attached to a + Listener and the trusted namespaces where those Route resources MAY be + present. + + Although a client request may match multiple route rules, only one rule + may ultimately receive the request. Matching precedence MUST be + determined in order of the following criteria: + + * The most specific match as defined by the Route type. + * The oldest Route based on creation timestamp. For example, a Route with + a creation timestamp of "2020-09-08 01:02:03" is given precedence over + a Route with a creation timestamp of "2020-09-08 01:02:04". + * If everything else is equivalent, the Route appearing first in + alphabetical order (namespace/name) should be given precedence. For + example, foo/bar is given precedence over foo/baz. + + All valid rules within a Route attached to this Listener should be + implemented. Invalid Route rules can be ignored (sometimes that will mean + the full Route). If a Route rule transitions from valid to invalid, + support for that Route rule should be dropped to ensure consistency. For + example, even if a filter specified by a Route rule is invalid, the rest + of the rules within that Route should still be supported. + + Support: Core + properties: + kinds: + description: |- + Kinds specifies the groups and kinds of Routes that are allowed to bind + to this Gateway Listener. When unspecified or empty, the kinds of Routes + selected are determined using the Listener protocol. + + A RouteGroupKind MUST correspond to kinds of Routes that are compatible + with the application protocol specified in the Listener's Protocol field. + If an implementation does not support or recognize this resource type, it + MUST set the "ResolvedRefs" condition to False for this Listener with the + "InvalidRouteKinds" reason. + + Support: Core + items: + description: RouteGroupKind indicates the group and kind + of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: |- + Namespaces indicates namespaces from which Routes may be attached to this + Listener. This is restricted to the namespace of this Gateway by default. + + Support: Core + properties: + from: + default: Same + description: |- + From indicates where Routes will be selected for this Gateway. Possible + values are: + + * All: Routes in all namespaces may be used by this Gateway. + * Selector: Routes in namespaces selected by the selector may be used by + this Gateway. + * Same: Only Routes in the same namespace may be used by this Gateway. + + Support: Core + enum: + - All + - Selector + - Same + type: string + selector: + description: |- + Selector must be specified when From is set to "Selector". In that case, + only Routes in Namespaces matching this Selector will be selected by this + Gateway. This field is ignored for other values of "From". + + Support: Core + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + type: object + type: object + hostname: + description: |- + Hostname specifies the virtual hostname to match for protocol types that + define this concept. When unspecified, all hostnames are matched. This + field is ignored for protocols that don't require hostname based + matching. + + Implementations MUST apply Hostname matching appropriately for each of + the following protocols: + + * TLS: The Listener Hostname MUST match the SNI. + * HTTP: The Listener Hostname MUST match the Host header of the request. + * HTTPS: The Listener Hostname SHOULD match both the SNI and Host header. + Note that this does not require the SNI and Host header to be the same. + The semantics of this are described in more detail below. + + To ensure security, Section 11.1 of RFC-6066 emphasizes that server + implementations that rely on SNI hostname matching MUST also verify + hostnames within the application protocol. + + Section 9.1.2 of RFC-7540 provides a mechanism for servers to reject the + reuse of a connection by responding with the HTTP 421 Misdirected Request + status code. This indicates that the origin server has rejected the + request because it appears to have been misdirected. + + To detect misdirected requests, Gateways SHOULD match the authority of + the requests with all the SNI hostname(s) configured across all the + Gateway Listeners on the same port and protocol: + + * If another Listener has an exact match or more specific wildcard entry, + the Gateway SHOULD return a 421. + * If the current Listener (selected by SNI matching during ClientHello) + does not match the Host: + * If another Listener does match the Host the Gateway SHOULD return a + 421. + * If no other Listener matches the Host, the Gateway MUST return a + 404. + + For HTTPRoute and TLSRoute resources, there is an interaction with the + `spec.hostnames` array. When both listener and route specify hostnames, + there MUST be an intersection between the values for a Route to be + accepted. For more information, refer to the Route specific Hostnames + documentation. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + name: + description: |- + Name is the name of the Listener. This name MUST be unique within a + Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: |- + Port is the network port. Multiple listeners may use the + same port, subject to the Listener compatibility rules. + + Support: Core + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: |- + Protocol specifies the network protocol this listener expects to receive. + + Support: Core + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: |- + TLS is the TLS configuration for the Listener. This field is required if + the Protocol field is "HTTPS" or "TLS". It is invalid to set this field + if the Protocol field is "HTTP", "TCP", or "UDP". + + The association of SNIs to Certificate defined in GatewayTLSConfig is + defined based on the Hostname field for this listener. + + The GatewayClass MUST use the longest matching SNI out of all + available certificates for any TLS handshake. + + Support: Core + properties: + certificateRefs: + description: |- + CertificateRefs contains a series of references to Kubernetes objects that + contains TLS certificates and private keys. These certificates are used to + establish a TLS handshake for requests that match the hostname of the + associated listener. + + A single CertificateRef to a Kubernetes Secret has "Core" support. + Implementations MAY choose to support attaching multiple certificates to + a Listener, but this behavior is implementation-specific. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + + This field is required to have at least one element when the mode is set + to "Terminate" (default) and is optional otherwise. + + CertificateRefs can reference to standard Kubernetes resources, i.e. + Secret, or implementation-specific custom resources. + + Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls + + Support: Implementation-specific (More than one reference or other resource types) + items: + description: |- + SecretObjectReference identifies an API object including its namespace, + defaulting to Secret. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example + "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + mode: + default: Terminate + description: |- + Mode defines the TLS behavior for the TLS session initiated by the client. + There are two possible modes: + + - Terminate: The TLS session between the downstream client and the + Gateway is terminated at the Gateway. This mode requires certificates + to be specified in some way, such as populating the certificateRefs + field. + - Passthrough: The TLS session is NOT terminated by the Gateway. This + implies that the Gateway can't decipher the TLS stream except for + the ClientHello message of the TLS protocol. The certificateRefs field + is ignored in this mode. + + Support: Core + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Options are a list of key/value pairs to enable extended TLS + configuration for each implementation. For example, configuring the + minimum TLS version or supported cipher suites. + + A set of common keys MAY be defined by the API in the future. To avoid + any ambiguity, implementation-specific definitions MUST use + domain-prefixed names, such as `example.com/my-custom-option`. + Un-prefixed names are reserved for key names defined by Gateway API. + + Support: Implementation-specific + maxProperties: 16 + type: object + type: object + x-kubernetes-validations: + - message: certificateRefs or options must be specified when + mode is Terminate + rule: 'self.mode == ''Terminate'' ? size(self.certificateRefs) + > 0 || size(self.options) > 0 : true' + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: tls must not be specified for protocols ['HTTP', 'TCP', + 'UDP'] + rule: 'self.all(l, l.protocol in [''HTTP'', ''TCP'', ''UDP''] ? + !has(l.tls) : true)' + - message: tls mode must be Terminate for protocol HTTPS + rule: 'self.all(l, (l.protocol == ''HTTPS'' && has(l.tls)) ? (l.tls.mode + == '''' || l.tls.mode == ''Terminate'') : true)' + - message: hostname must not be specified for protocols ['TCP', 'UDP'] + rule: 'self.all(l, l.protocol in [''TCP'', ''UDP''] ? (!has(l.hostname) + || l.hostname == '''') : true)' + - message: Listener name must be unique within the Gateway + rule: self.all(l1, self.exists_one(l2, l1.name == l2.name)) + - message: Combination of port, protocol and hostname must be unique + for each listener + rule: 'self.all(l1, self.exists_one(l2, l1.port == l2.port && l1.protocol + == l2.protocol && (has(l1.hostname) && has(l2.hostname) ? l1.hostname + == l2.hostname : !has(l1.hostname) && !has(l2.hostname))))' + required: + - gatewayClassName + - listeners + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: Status defines the current state of Gateway. + properties: + addresses: + description: |- + Addresses lists the network addresses that have been bound to the + Gateway. + + This list may differ from the addresses provided in the spec under some + conditions: + + * no addresses are specified, all addresses are dynamically assigned + * a combination of specified and dynamic addresses are assigned + * a specified address was unusable (e.g. already in use) + items: + description: GatewayStatusAddress describes a network address that + is bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: |- + Value of the address. The validity of the values will depend + on the type and support by the controller. + + Examples: `1.2.3.4`, `128::1`, `my-ip-address`. + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching + ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + true' + maxItems: 16 + type: array + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: |- + Conditions describe the current conditions of the Gateway. + + Implementations should prefer to express Gateway conditions + using the `GatewayConditionType` and `GatewayConditionReason` + constants so that operators and tools can converge on a common + vocabulary to describe Gateway state. + + Known condition types are: + + * "Accepted" + * "Programmed" + * "Ready" + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port + defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: |- + AttachedRoutes represents the total number of Routes that have been + successfully attached to this Listener. + + Successful attachment of a Route to a Listener is based solely on the + combination of the AllowedRoutes field on the corresponding Listener + and the Route's ParentRefs field. A Route is successfully attached to + a Listener when it is selected by the Listener's AllowedRoutes field + AND the Route has a valid ParentRef selecting the whole Gateway + resource or a specific Listener as a parent resource (more detail on + attachment semantics can be found in the documentation on the various + Route kinds ParentRefs fields). Listener or Route status does not impact + successful attachment, i.e. the AttachedRoutes field count MUST be set + for Listeners with condition Accepted: false and MUST count successfully + attached Routes that may themselves have Accepted: false conditions. + + Uses for this field include troubleshooting Route attachment and + measuring blast radius/impact of changes to a Listener. + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this + listener. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status + corresponds to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + supportedKinds: + description: |- + SupportedKinds is the list indicating the Kinds supported by this + listener. This MUST represent the kinds an implementation supports for + that Listener configuration. + + If kinds are specified in Spec that are not supported, they MUST NOT + appear in this list and an implementation MUST set the "ResolvedRefs" + condition to "False" with the "InvalidRouteKinds" reason. If both valid + and invalid Route kinds are specified, the implementation MUST + reference the valid Route kinds that have been specified. + items: + description: RouteGroupKind indicates the group and kind of + a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# +# config/crd/standard/gateway.networking.k8s.io_grpcroutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: standard + creationTimestamp: null + name: grpcroutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: GRPCRoute + listKind: GRPCRouteList + plural: grpcroutes + singular: grpcroute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + GRPCRoute provides a way to route gRPC requests. This includes the capability + to match requests by hostname, gRPC service, gRPC method, or HTTP/2 header. + Filters can be used to specify additional processing steps. Backends specify + where matching requests will be routed. + + GRPCRoute falls under extended support within the Gateway API. Within the + following specification, the word "MUST" indicates that an implementation + supporting GRPCRoute must conform to the indicated requirement, but an + implementation not supporting this route type need not follow the requirement + unless explicitly indicated. + + Implementations supporting `GRPCRoute` with the `HTTPS` `ProtocolType` MUST + accept HTTP/2 connections without an initial upgrade from HTTP/1.1, i.e. via + ALPN. If the implementation does not support this, then it MUST set the + "Accepted" condition to "False" for the affected listener with a reason of + "UnsupportedProtocol". Implementations MAY also accept HTTP/2 connections + with an upgrade from HTTP/1. + + Implementations supporting `GRPCRoute` with the `HTTP` `ProtocolType` MUST + support HTTP/2 over cleartext TCP (h2c, + https://www.rfc-editor.org/rfc/rfc7540#section-3.1) without an initial + upgrade from HTTP/1.1, i.e. with prior knowledge + (https://www.rfc-editor.org/rfc/rfc7540#section-3.4). If the implementation + does not support this, then it MUST set the "Accepted" condition to "False" + for the affected listener with a reason of "UnsupportedProtocol". + Implementations MAY also accept HTTP/2 connections with an upgrade from + HTTP/1, i.e. without prior knowledge. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GRPCRoute. + properties: + hostnames: + description: |- + Hostnames defines a set of hostnames to match against the GRPC + Host header to select a GRPCRoute to process the request. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label MUST appear by itself as the first label. + + If a hostname is specified by both the Listener and GRPCRoute, there + MUST be at least one intersecting hostname for the GRPCRoute to be + attached to the Listener. For example: + + * A Listener with `test.example.com` as the hostname matches GRPCRoutes + that have either not specified any hostnames, or have specified at + least one of `test.example.com` or `*.example.com`. + * A Listener with `*.example.com` as the hostname matches GRPCRoutes + that have either not specified any hostnames or have specified at least + one hostname that matches the Listener hostname. For example, + `test.example.com` and `*.example.com` would both match. On the other + hand, `example.com` and `test.example.net` would not match. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + + If both the Listener and GRPCRoute have specified hostnames, any + GRPCRoute hostnames that do not match the Listener hostname MUST be + ignored. For example, if a Listener specified `*.example.com`, and the + GRPCRoute specified `test.example.com` and `test.example.net`, + `test.example.net` MUST NOT be considered for a match. + + If both the Listener and GRPCRoute have specified hostnames, and none + match with the criteria above, then the GRPCRoute MUST NOT be accepted by + the implementation. The implementation MUST raise an 'Accepted' Condition + with a status of `False` in the corresponding RouteParentStatus. + + If a Route (A) of type HTTPRoute or GRPCRoute is attached to a + Listener and that listener already has another Route (B) of the other + type attached and the intersection of the hostnames of A and B is + non-empty, then the implementation MUST accept exactly one of these two + routes, determined by the following criteria, in order: + + * The oldest Route based on creation timestamp. + * The Route appearing first in alphabetical order by + "{namespace}/{name}". + + The rejected Route MUST raise an 'Accepted' condition with a status of + 'False' in the corresponding RouteParentStatus. + + Support: Core + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''')) : true))' + - message: sectionName must be unique when parentRefs includes 2 or + more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || (has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)))) + rules: + description: Rules are a list of GRPC matchers, filters and actions. + items: + description: |- + GRPCRouteRule defines the semantics for matching a gRPC request based on + conditions (matches), processing it (filters), and forwarding the request to + an API object (backendRefs). + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. + + Failure behavior here depends on how many BackendRefs are specified and + how many are invalid. + + If *all* entries in BackendRefs are invalid, and there are also no filters + specified in this route rule, *all* traffic which matches this rule MUST + receive an `UNAVAILABLE` status. + + See the GRPCBackendRef definition for the rules about what makes a single + GRPCBackendRef invalid. + + When a GRPCBackendRef is invalid, `UNAVAILABLE` statuses MUST be returned for + requests that would have otherwise been routed to an invalid backend. If + multiple backends are specified, and some are invalid, the proportion of + requests that would otherwise have been routed to an invalid backend + MUST receive an `UNAVAILABLE` status. + + For example, if two backends are specified with equal weights, and one is + invalid, 50 percent of traffic MUST receive an `UNAVAILABLE` status. + Implementations may choose how that 50 percent is determined. + + Support: Core for Kubernetes Service + + Support: Implementation-specific for any other resource + + Support for weight: Core + items: + description: |- + GRPCBackendRef defines how a GRPCRoute forwards a gRPC request. + + Note that when a namespace different than the local namespace is specified, a + ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + properties: + filters: + description: |- + Filters defined at this level MUST be executed if and only if the + request is being forwarded to the backend defined here. + + Support: Implementation-specific (For broader support of filters, use the + Filters field in GRPCRouteRule.) + items: + description: |- + GRPCRouteFilter defines processing steps that must be completed during the + request or response lifecycle. GRPCRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + Support: Implementation-specific + + This filter can be used multiple times within the same rule. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal + to denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be + specified in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations supporting GRPCRoute MUST support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` MUST be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + enum: + - ResponseHeaderModifier + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil + if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type + != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type + == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil + if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type + != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for + RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == + ''RequestMirror'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for + ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + filters: + description: |- + Filters define the filters that are applied to requests that match + this rule. + + The effects of ordering of multiple behaviors are currently unspecified. + This can change in the future based on feedback during the alpha stage. + + Conformance-levels at this level are defined based on the type of filter: + + - ALL core filters MUST be supported by all implementations that support + GRPCRoute. + - Implementers are encouraged to support extended filters. + - Implementation-specific custom filters have no API guarantees across + implementations. + + Specifying the same filter multiple times is not supported unless explicitly + indicated in the filter. + + If an implementation cannot support a combination of filters, it must clearly + document that limitation. In cases where incompatible or unsupported + filters are specified and cause the `Accepted` condition to be set to status + `False`, implementations may use the `IncompatibleFilters` reason to specify + this configuration error. + + Support: Core + items: + description: |- + GRPCRouteFilter defines processing steps that must be completed during the + request or response lifecycle. GRPCRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + Support: Implementation-specific + + This filter can be used multiple times within the same rule. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal to + denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be specified + in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations supporting GRPCRoute MUST support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` MUST be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + enum: + - ResponseHeaderModifier + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the + filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != + ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == + ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the + filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != + ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror + filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef + filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + matches: + description: |- + Matches define conditions used for matching the rule against incoming + gRPC requests. Each match is independent, i.e. this rule will be matched + if **any** one of the matches is satisfied. + + For example, take the following matches configuration: + + ``` + matches: + - method: + service: foo.bar + headers: + values: + version: 2 + - method: + service: foo.bar.v2 + ``` + + For a request to match against this rule, it MUST satisfy + EITHER of the two conditions: + + - service of foo.bar AND contains the header `version: 2` + - service of foo.bar.v2 + + See the documentation for GRPCRouteMatch on how to specify multiple + match conditions to be ANDed together. + + If no matches are specified, the implementation MUST match every gRPC request. + + Proxy or Load Balancer routing configuration generated from GRPCRoutes + MUST prioritize rules based on the following criteria, continuing on + ties. Merging MUST not be done between GRPCRoutes and HTTPRoutes. + Precedence MUST be given to the rule with the largest number of: + + * Characters in a matching non-wildcard hostname. + * Characters in a matching hostname. + * Characters in a matching service. + * Characters in a matching method. + * Header matches. + + If ties still exist across multiple Routes, matching precedence MUST be + determined in order of the following criteria, continuing on ties: + + * The oldest Route based on creation timestamp. + * The Route appearing first in alphabetical order by + "{namespace}/{name}". + + If ties still exist within the Route that has been given precedence, + matching precedence MUST be granted to the first matching rule meeting + the above criteria. + items: + description: |- + GRPCRouteMatch defines the predicate used to match requests to a given + action. Multiple match types are ANDed together, i.e. the match will + evaluate to true only if all conditions are satisfied. + + For example, the match below will match a gRPC request only if its service + is `foo` AND it contains the `version: v1` header: + + ``` + matches: + - method: + type: Exact + service: "foo" + headers: + - name: "version" + value "v1" + + ``` + properties: + headers: + description: |- + Headers specifies gRPC request header matchers. Multiple match values are + ANDed together, meaning, a request MUST match all the specified headers + to select the route. + items: + description: |- + GRPCHeaderMatch describes how to select a gRPC route by matching gRPC request + headers. + properties: + name: + description: |- + Name is the name of the gRPC Header to be matched. + + If multiple entries specify equivalent header names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: Type specifies how to match against + the value of the header. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of the gRPC Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: |- + Method specifies a gRPC request service/method matcher. If this field is + not specified, all services and methods will match. + properties: + method: + description: |- + Value of the method to match against. If left empty or omitted, will + match all services. + + At least one of Service and Method MUST be a non-empty string. + maxLength: 1024 + type: string + service: + description: |- + Value of the service to match against. If left empty or omitted, will + match any service. + + At least one of Service and Method MUST be a non-empty string. + maxLength: 1024 + type: string + type: + default: Exact + description: |- + Type specifies how to match against the service and/or method. + Support: Core (Exact with service and method specified) + + Support: Implementation-specific (Exact with method specified but no service specified) + + Support: Implementation-specific (RegularExpression) + enum: + - Exact + - RegularExpression + type: string + type: object + x-kubernetes-validations: + - message: One or both of 'service' or 'method' must be + specified + rule: 'has(self.type) ? has(self.service) || has(self.method) + : true' + - message: service must only contain valid characters + (matching ^(?i)\.?[a-z_][a-z_0-9]*(\.[a-z_][a-z_0-9]*)*$) + rule: '(!has(self.type) || self.type == ''Exact'') && + has(self.service) ? self.service.matches(r"""^(?i)\.?[a-z_][a-z_0-9]*(\.[a-z_][a-z_0-9]*)*$"""): + true' + - message: method must only contain valid characters (matching + ^[A-Za-z_][A-Za-z_0-9]*$) + rule: '(!has(self.type) || self.type == ''Exact'') && + has(self.method) ? self.method.matches(r"""^[A-Za-z_][A-Za-z_0-9]*$"""): + true' + type: object + maxItems: 64 + type: array + type: object + maxItems: 16 + type: array + x-kubernetes-validations: + - message: While 16 rules and 64 matches per rule are allowed, the + total number of matches across all rules in a route must be less + than 128 + rule: '(self.size() > 0 ? (has(self[0].matches) ? self[0].matches.size() + : 0) : 0) + (self.size() > 1 ? (has(self[1].matches) ? self[1].matches.size() + : 0) : 0) + (self.size() > 2 ? (has(self[2].matches) ? self[2].matches.size() + : 0) : 0) + (self.size() > 3 ? (has(self[3].matches) ? self[3].matches.size() + : 0) : 0) + (self.size() > 4 ? (has(self[4].matches) ? self[4].matches.size() + : 0) : 0) + (self.size() > 5 ? (has(self[5].matches) ? self[5].matches.size() + : 0) : 0) + (self.size() > 6 ? (has(self[6].matches) ? self[6].matches.size() + : 0) : 0) + (self.size() > 7 ? (has(self[7].matches) ? self[7].matches.size() + : 0) : 0) + (self.size() > 8 ? (has(self[8].matches) ? self[8].matches.size() + : 0) : 0) + (self.size() > 9 ? (has(self[9].matches) ? self[9].matches.size() + : 0) : 0) + (self.size() > 10 ? (has(self[10].matches) ? self[10].matches.size() + : 0) : 0) + (self.size() > 11 ? (has(self[11].matches) ? self[11].matches.size() + : 0) : 0) + (self.size() > 12 ? (has(self[12].matches) ? self[12].matches.size() + : 0) : 0) + (self.size() > 13 ? (has(self[13].matches) ? self[13].matches.size() + : 0) : 0) + (self.size() > 14 ? (has(self[14].matches) ? self[14].matches.size() + : 0) : 0) + (self.size() > 15 ? (has(self[15].matches) ? self[15].matches.size() + : 0) : 0) <= 128' + type: object + status: + description: Status defines the current state of GRPCRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# +# config/crd/standard/gateway.networking.k8s.io_httproutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: standard + creationTimestamp: null + name: httproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: HTTPRoute + listKind: HTTPRouteList + plural: httproutes + singular: httproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + HTTPRoute provides a way to route HTTP requests. This includes the capability + to match requests by hostname, path, header, or query param. Filters can be + used to specify additional processing steps. Backends specify where matching + requests should be routed. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of HTTPRoute. + properties: + hostnames: + description: |- + Hostnames defines a set of hostnames that should match against the HTTP Host + header to select a HTTPRoute used to process the request. Implementations + MUST ignore any port value specified in the HTTP Host header while + performing a match and (absent of any applicable header modification + configuration) MUST forward this header unmodified to the backend. + + Valid values for Hostnames are determined by RFC 1123 definition of a + hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + If a hostname is specified by both the Listener and HTTPRoute, there + must be at least one intersecting hostname for the HTTPRoute to be + attached to the Listener. For example: + + * A Listener with `test.example.com` as the hostname matches HTTPRoutes + that have either not specified any hostnames, or have specified at + least one of `test.example.com` or `*.example.com`. + * A Listener with `*.example.com` as the hostname matches HTTPRoutes + that have either not specified any hostnames or have specified at least + one hostname that matches the Listener hostname. For example, + `*.example.com`, `test.example.com`, and `foo.test.example.com` would + all match. On the other hand, `example.com` and `test.example.net` would + not match. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + + If both the Listener and HTTPRoute have specified hostnames, any + HTTPRoute hostnames that do not match the Listener hostname MUST be + ignored. For example, if a Listener specified `*.example.com`, and the + HTTPRoute specified `test.example.com` and `test.example.net`, + `test.example.net` must not be considered for a match. + + If both the Listener and HTTPRoute have specified hostnames, and none + match with the criteria above, then the HTTPRoute is not accepted. The + implementation must raise an 'Accepted' Condition with a status of + `False` in the corresponding RouteParentStatus. + + In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. + overlapping wildcard matching and exact matching hostnames), precedence must + be given to rules from the HTTPRoute with the largest number of: + + * Characters in a matching non-wildcard hostname. + * Characters in a matching hostname. + + If ties exist across multiple Routes, the matching precedence rules for + HTTPRouteMatches takes over. + + Support: Core + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''')) : true))' + - message: sectionName must be unique when parentRefs includes 2 or + more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || (has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)))) + rules: + default: + - matches: + - path: + type: PathPrefix + value: / + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: |- + HTTPRouteRule defines semantics for matching an HTTP request based on + conditions (matches), processing it (filters), and forwarding the request to + an API object (backendRefs). + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. + + Failure behavior here depends on how many BackendRefs are specified and + how many are invalid. + + If *all* entries in BackendRefs are invalid, and there are also no filters + specified in this route rule, *all* traffic which matches this rule MUST + receive a 500 status code. + + See the HTTPBackendRef definition for the rules about what makes a single + HTTPBackendRef invalid. + + When a HTTPBackendRef is invalid, 500 status codes MUST be returned for + requests that would have otherwise been routed to an invalid backend. If + multiple backends are specified, and some are invalid, the proportion of + requests that would otherwise have been routed to an invalid backend + MUST receive a 500 status code. + + For example, if two backends are specified with equal weights, and one is + invalid, 50 percent of traffic must receive a 500. Implementations may + choose how that 50 percent is determined. + + When a HTTPBackendRef refers to a Service that has no ready endpoints, + implementations SHOULD return a 503 for requests to that backend instead. + If an implementation chooses to do this, all of the above rules for 500 responses + MUST also apply for responses that return a 503. + + Support: Core for Kubernetes Service + + Support: Extended for Kubernetes ServiceImport + + Support: Implementation-specific for any other resource + + Support for weight: Core + items: + description: |- + HTTPBackendRef defines how a HTTPRoute forwards a HTTP request. + + Note that when a namespace different than the local namespace is specified, a + ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + properties: + filters: + description: |- + Filters defined at this level should be executed if and only if the + request is being forwarded to the backend defined here. + + Support: Implementation-specific (For broader support of filters, use the + Filters field in HTTPRouteRule.) + items: + description: |- + HTTPRouteFilter defines processing steps that must be completed during the + request or response lifecycle. HTTPRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + This filter can be used multiple times within the same rule. + + Support: Implementation-specific + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal + to denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be + specified in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + requestRedirect: + description: |- + RequestRedirect defines a schema for a filter that responds to the + request with an HTTP redirection. + + Support: Core + properties: + hostname: + description: |- + Hostname is the hostname to be used in the value of the `Location` + header in the response. + When empty, the hostname in the `Host` header of the request is used. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines parameters used to modify the path of the incoming request. + The modified path is then used to construct the `Location` header. When + empty, the request path is used as-is. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified + when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? + has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when + replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type + == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified + when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' + ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' + when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + port: + description: |- + Port is the port to be used in the value of the `Location` + header in the response. + + If no port is specified, the redirect port MUST be derived using the + following rules: + + * If redirect scheme is not-empty, the redirect port MUST be the well-known + port associated with the redirect scheme. Specifically "http" to port 80 + and "https" to port 443. If the redirect scheme does not have a + well-known port, the listener port of the Gateway SHOULD be used. + * If redirect scheme is empty, the redirect port MUST be the Gateway + Listener port. + + Implementations SHOULD NOT add the port number in the 'Location' + header in the following cases: + + * A Location header that will use HTTP (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 80. + * A Location header that will use HTTPS (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 443. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: |- + Scheme is the scheme to be used in the value of the `Location` header in + the response. When empty, the scheme of the request is used. + + Scheme redirects can affect the port of the redirect, for more information, + refer to the documentation for the port field of this filter. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Extended + enum: + - http + - https + type: string + statusCode: + default: 302 + description: |- + StatusCode is the HTTP status code to be used in response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Core + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations must support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by + specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` should be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: |- + URLRewrite defines a schema for a filter that modifies a request during forwarding. + + Support: Extended + properties: + hostname: + description: |- + Hostname is the value to be used to replace the Host header value during + forwarding. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines a path rewrite. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified + when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? + has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when + replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type + == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified + when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' + ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' + when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil + if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type + != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type + == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil + if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type + != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for + RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == + ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the + filter.type is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != + ''RequestRedirect'')' + - message: filter.requestRedirect must be specified + for RequestRedirect filter.type + rule: '!(!has(self.requestRedirect) && self.type == + ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type + is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite + filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for + ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') + && self.exists(f, f.type == ''URLRewrite''))' + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') + && self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() + <= 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() + <= 1 + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + filters: + description: |- + Filters define the filters that are applied to requests that match + this rule. + + Wherever possible, implementations SHOULD implement filters in the order + they are specified. + + Implementations MAY choose to implement this ordering strictly, rejecting + any combination or order of filters that cannot be supported. If implementations + choose a strict interpretation of filter ordering, they MUST clearly document + that behavior. + + To reject an invalid combination or order of filters, implementations SHOULD + consider the Route Rules with this configuration invalid. If all Route Rules + in a Route are invalid, the entire Route would be considered invalid. If only + a portion of Route Rules are invalid, implementations MUST set the + "PartiallyInvalid" condition for the Route. + + Conformance-levels at this level are defined based on the type of filter: + + - ALL core filters MUST be supported by all implementations. + - Implementers are encouraged to support extended filters. + - Implementation-specific custom filters have no API guarantees across + implementations. + + Specifying the same filter multiple times is not supported unless explicitly + indicated in the filter. + + All filters are expected to be compatible with each other except for the + URLRewrite and RequestRedirect filters, which may not be combined. If an + implementation cannot support other combinations of filters, they must clearly + document that limitation. In cases where incompatible or unsupported + filters are specified and cause the `Accepted` condition to be set to status + `False`, implementations may use the `IncompatibleFilters` reason to specify + this configuration error. + + Support: Core + items: + description: |- + HTTPRouteFilter defines processing steps that must be completed during the + request or response lifecycle. HTTPRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + This filter can be used multiple times within the same rule. + + Support: Implementation-specific + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal to + denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be specified + in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + requestRedirect: + description: |- + RequestRedirect defines a schema for a filter that responds to the + request with an HTTP redirection. + + Support: Core + properties: + hostname: + description: |- + Hostname is the hostname to be used in the value of the `Location` + header in the response. + When empty, the hostname in the `Host` header of the request is used. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines parameters used to modify the path of the incoming request. + The modified path is then used to construct the `Location` header. When + empty, the request path is used as-is. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when + type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) + : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath + is set + rule: 'has(self.replaceFullPath) ? self.type == + ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when + type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) + : true' + - message: type must be 'ReplacePrefixMatch' when + replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + port: + description: |- + Port is the port to be used in the value of the `Location` + header in the response. + + If no port is specified, the redirect port MUST be derived using the + following rules: + + * If redirect scheme is not-empty, the redirect port MUST be the well-known + port associated with the redirect scheme. Specifically "http" to port 80 + and "https" to port 443. If the redirect scheme does not have a + well-known port, the listener port of the Gateway SHOULD be used. + * If redirect scheme is empty, the redirect port MUST be the Gateway + Listener port. + + Implementations SHOULD NOT add the port number in the 'Location' + header in the following cases: + + * A Location header that will use HTTP (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 80. + * A Location header that will use HTTPS (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 443. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: |- + Scheme is the scheme to be used in the value of the `Location` header in + the response. When empty, the scheme of the request is used. + + Scheme redirects can affect the port of the redirect, for more information, + refer to the documentation for the port field of this filter. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Extended + enum: + - http + - https + type: string + statusCode: + default: 302 + description: |- + StatusCode is the HTTP status code to be used in response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Core + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations must support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by + specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` should be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: |- + URLRewrite defines a schema for a filter that modifies a request during forwarding. + + Support: Extended + properties: + hostname: + description: |- + Hostname is the value to be used to replace the Host header value during + forwarding. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines a path rewrite. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when + type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) + : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath + is set + rule: 'has(self.replaceFullPath) ? self.type == + ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when + type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) + : true' + - message: type must be 'ReplacePrefixMatch' when + replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the + filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != + ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == + ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the + filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != + ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror + filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the filter.type + is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')' + - message: filter.requestRedirect must be specified for RequestRedirect + filter.type + rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type + is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite + filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef + filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') && + self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() <= + 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() <= 1 + matches: + default: + - path: + type: PathPrefix + value: / + description: |- + Matches define conditions used for matching the rule against incoming + HTTP requests. Each match is independent, i.e. this rule will be matched + if **any** one of the matches is satisfied. + + For example, take the following matches configuration: + + ``` + matches: + - path: + value: "/foo" + headers: + - name: "version" + value: "v2" + - path: + value: "/v2/foo" + ``` + + For a request to match against this rule, a request must satisfy + EITHER of the two conditions: + + - path prefixed with `/foo` AND contains the header `version: v2` + - path prefix of `/v2/foo` + + See the documentation for HTTPRouteMatch on how to specify multiple + match conditions that should be ANDed together. + + If no matches are specified, the default is a prefix + path match on "/", which has the effect of matching every + HTTP request. + + Proxy or Load Balancer routing configuration generated from HTTPRoutes + MUST prioritize matches based on the following criteria, continuing on + ties. Across all rules specified on applicable Routes, precedence must be + given to the match having: + + * "Exact" path match. + * "Prefix" path match with largest number of characters. + * Method match. + * Largest number of header matches. + * Largest number of query param matches. + + Note: The precedence of RegularExpression path matches are implementation-specific. + + If ties still exist across multiple Routes, matching precedence MUST be + determined in order of the following criteria, continuing on ties: + + * The oldest Route based on creation timestamp. + * The Route appearing first in alphabetical order by + "{namespace}/{name}". + + If ties still exist within an HTTPRoute, matching precedence MUST be granted + to the FIRST matching rule (in list order) with a match meeting the above + criteria. + + When no rules matching a request have been successfully attached to the + parent a request is coming from, a HTTP 404 status code MUST be returned. + items: + description: "HTTPRouteMatch defines the predicate used to + match requests to a given\naction. Multiple match types + are ANDed together, i.e. the match will\nevaluate to true + only if all conditions are satisfied.\n\nFor example, the + match below will match a HTTP request only if its path\nstarts + with `/foo` AND it contains the `version: v1` header:\n\n```\nmatch:\n\n\tpath:\n\t + \ value: \"/foo\"\n\theaders:\n\t- name: \"version\"\n\t + \ value \"v1\"\n\n```" + properties: + headers: + description: |- + Headers specifies HTTP request header matchers. Multiple match values are + ANDed together, meaning, a request must match all the specified headers + to select the route. + items: + description: |- + HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request + headers. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + + When a header is repeated in an HTTP request, it is + implementation-specific behavior as to how this is represented. + Generally, proxies should follow the guidance from the RFC: + https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding + processing a repeated header, with special handling for "Set-Cookie". + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the header. + + Support: Core (Exact) + + Support: Implementation-specific (RegularExpression) + + Since RegularExpression HeaderMatchType has implementation-specific + conformance, implementations can support POSIX, PCRE or any other dialects + of regular expressions. Please read the implementation's documentation to + determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP Header to + be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: |- + Method specifies HTTP method matcher. + When specified, this route will be matched only if the request has the + specified method. + + Support: Extended + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: |- + Path specifies a HTTP request path matcher. If this field is not + specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: |- + Type specifies how to match against the path Value. + + Support: Core (Exact, PathPrefix) + + Support: Implementation-specific (RegularExpression) + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path to match against. + maxLength: 1024 + type: string + type: object + x-kubernetes-validations: + - message: value must be an absolute path and start with + '/' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.startsWith(''/'') + : true' + - message: must not contain '//' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''//'') + : true' + - message: must not contain '/./' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/./'') + : true' + - message: must not contain '/../' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/../'') + : true' + - message: must not contain '%2f' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2f'') + : true' + - message: must not contain '%2F' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2F'') + : true' + - message: must not contain '#' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''#'') + : true' + - message: must not end with '/..' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/..'') + : true' + - message: must not end with '/.' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/.'') + : true' + - message: type must be one of ['Exact', 'PathPrefix', + 'RegularExpression'] + rule: self.type in ['Exact','PathPrefix'] || self.type + == 'RegularExpression' + - message: must only contain valid characters (matching + ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$) + for types ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.matches(r"""^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$""") + : true' + queryParams: + description: |- + QueryParams specifies HTTP query parameter matchers. Multiple match + values are ANDed together, meaning, a request must match all the + specified query parameters to select the route. + + Support: Extended + items: + description: |- + HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP + query parameters. + properties: + name: + description: |- + Name is the name of the HTTP query param to be matched. This must be an + exact string match. (See + https://tools.ietf.org/html/rfc7230#section-2.7.3). + + If multiple entries specify equivalent query param names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent query param name MUST be ignored. + + If a query param is repeated in an HTTP request, the behavior is + purposely left undefined, since different data planes have different + capabilities. However, it is *recommended* that implementations should + match against the first value of the param if the data plane supports it, + as this behavior is expected in other load balancing contexts outside of + the Gateway API. + + Users SHOULD NOT route traffic based on repeated query params to guard + themselves against potential differences in the implementations. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the query parameter. + + Support: Extended (Exact) + + Support: Implementation-specific (RegularExpression) + + Since RegularExpression QueryParamMatchType has Implementation-specific + conformance, implementations can support POSIX, PCRE or any other + dialects of regular expressions. Please read the implementation's + documentation to determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP query param + to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 64 + type: array + timeouts: + description: |- + Timeouts defines the timeouts that can be configured for an HTTP request. + + Support: Extended + properties: + backendRequest: + description: |- + BackendRequest specifies a timeout for an individual request from the gateway + to a backend. This covers the time from when the request first starts being + sent from the gateway to when the full response has been received from the backend. + + Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout + completely. Implementations that cannot completely disable the timeout MUST + instead interpret the zero duration as the longest possible value to which + the timeout can be set. + + An entire client HTTP transaction with a gateway, covered by the Request timeout, + may result in more than one call from the gateway to the destination backend, + for example, if automatic retries are supported. + + The value of BackendRequest must be a Gateway API Duration string as defined by + GEP-2257. When this field is unspecified, its behavior is implementation-specific; + when specified, the value of BackendRequest must be no more than the value of the + Request timeout (since the Request timeout encompasses the BackendRequest timeout). + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + request: + description: |- + Request specifies the maximum duration for a gateway to respond to an HTTP request. + If the gateway has not been able to respond before this deadline is met, the gateway + MUST return a timeout error. + + For example, setting the `rules.timeouts.request` field to the value `10s` in an + `HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds + to complete. + + Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout + completely. Implementations that cannot completely disable the timeout MUST + instead interpret the zero duration as the longest possible value to which + the timeout can be set. + + This timeout is intended to cover as close to the whole request-response transaction + as possible although an implementation MAY choose to start the timeout after the entire + request stream has been received instead of immediately after the transaction is + initiated by the client. + + The value of Request is a Gateway API Duration string as defined by GEP-2257. When this + field is unspecified, request timeout behavior is implementation-specific. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + x-kubernetes-validations: + - message: backendRequest timeout cannot be longer than request + timeout + rule: '!(has(self.request) && has(self.backendRequest) && + duration(self.request) != duration(''0s'') && duration(self.backendRequest) + > duration(self.request))' + type: object + x-kubernetes-validations: + - message: RequestRedirect filter must not be used together with + backendRefs + rule: '(has(self.backendRefs) && size(self.backendRefs) > 0) ? + (!has(self.filters) || self.filters.all(f, !has(f.requestRedirect))): + true' + - message: When using RequestRedirect filter with path.replacePrefixMatch, + exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.requestRedirect) + && has(f.requestRedirect.path) && f.requestRedirect.path.type + == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) + ? ((size(self.matches) != 1 || !has(self.matches[0].path) || + self.matches[0].path.type != ''PathPrefix'') ? false : true) + : true' + - message: When using URLRewrite filter with path.replacePrefixMatch, + exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.urlRewrite) + && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' + && has(f.urlRewrite.path.replacePrefixMatch))) ? ((size(self.matches) + != 1 || !has(self.matches[0].path) || self.matches[0].path.type + != ''PathPrefix'') ? false : true) : true' + - message: Within backendRefs, when using RequestRedirect filter + with path.replacePrefixMatch, exactly one PathPrefix match must + be specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, + (has(b.filters) && b.filters.exists_one(f, has(f.requestRedirect) + && has(f.requestRedirect.path) && f.requestRedirect.path.type + == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) + )) ? ((size(self.matches) != 1 || !has(self.matches[0].path) + || self.matches[0].path.type != ''PathPrefix'') ? false : true) + : true' + - message: Within backendRefs, When using URLRewrite filter with + path.replacePrefixMatch, exactly one PathPrefix match must be + specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, + (has(b.filters) && b.filters.exists_one(f, has(f.urlRewrite) + && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' + && has(f.urlRewrite.path.replacePrefixMatch))) )) ? ((size(self.matches) + != 1 || !has(self.matches[0].path) || self.matches[0].path.type + != ''PathPrefix'') ? false : true) : true' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: While 16 rules and 64 matches per rule are allowed, the + total number of matches across all rules in a route must be less + than 128 + rule: '(self.size() > 0 ? self[0].matches.size() : 0) + (self.size() + > 1 ? self[1].matches.size() : 0) + (self.size() > 2 ? self[2].matches.size() + : 0) + (self.size() > 3 ? self[3].matches.size() : 0) + (self.size() + > 4 ? self[4].matches.size() : 0) + (self.size() > 5 ? self[5].matches.size() + : 0) + (self.size() > 6 ? self[6].matches.size() : 0) + (self.size() + > 7 ? self[7].matches.size() : 0) + (self.size() > 8 ? self[8].matches.size() + : 0) + (self.size() > 9 ? self[9].matches.size() : 0) + (self.size() + > 10 ? self[10].matches.size() : 0) + (self.size() > 11 ? self[11].matches.size() + : 0) + (self.size() > 12 ? self[12].matches.size() : 0) + (self.size() + > 13 ? self[13].matches.size() : 0) + (self.size() > 14 ? self[14].matches.size() + : 0) + (self.size() > 15 ? self[15].matches.size() : 0) <= 128' + type: object + status: + description: Status defines the current state of HTTPRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: |- + HTTPRoute provides a way to route HTTP requests. This includes the capability + to match requests by hostname, path, header, or query param. Filters can be + used to specify additional processing steps. Backends specify where matching + requests should be routed. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of HTTPRoute. + properties: + hostnames: + description: |- + Hostnames defines a set of hostnames that should match against the HTTP Host + header to select a HTTPRoute used to process the request. Implementations + MUST ignore any port value specified in the HTTP Host header while + performing a match and (absent of any applicable header modification + configuration) MUST forward this header unmodified to the backend. + + Valid values for Hostnames are determined by RFC 1123 definition of a + hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + If a hostname is specified by both the Listener and HTTPRoute, there + must be at least one intersecting hostname for the HTTPRoute to be + attached to the Listener. For example: + + * A Listener with `test.example.com` as the hostname matches HTTPRoutes + that have either not specified any hostnames, or have specified at + least one of `test.example.com` or `*.example.com`. + * A Listener with `*.example.com` as the hostname matches HTTPRoutes + that have either not specified any hostnames or have specified at least + one hostname that matches the Listener hostname. For example, + `*.example.com`, `test.example.com`, and `foo.test.example.com` would + all match. On the other hand, `example.com` and `test.example.net` would + not match. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + + If both the Listener and HTTPRoute have specified hostnames, any + HTTPRoute hostnames that do not match the Listener hostname MUST be + ignored. For example, if a Listener specified `*.example.com`, and the + HTTPRoute specified `test.example.com` and `test.example.net`, + `test.example.net` must not be considered for a match. + + If both the Listener and HTTPRoute have specified hostnames, and none + match with the criteria above, then the HTTPRoute is not accepted. The + implementation must raise an 'Accepted' Condition with a status of + `False` in the corresponding RouteParentStatus. + + In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. + overlapping wildcard matching and exact matching hostnames), precedence must + be given to rules from the HTTPRoute with the largest number of: + + * Characters in a matching non-wildcard hostname. + * Characters in a matching hostname. + + If ties exist across multiple Routes, the matching precedence rules for + HTTPRouteMatches takes over. + + Support: Core + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''')) : true))' + - message: sectionName must be unique when parentRefs includes 2 or + more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || (has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)))) + rules: + default: + - matches: + - path: + type: PathPrefix + value: / + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: |- + HTTPRouteRule defines semantics for matching an HTTP request based on + conditions (matches), processing it (filters), and forwarding the request to + an API object (backendRefs). + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. + + Failure behavior here depends on how many BackendRefs are specified and + how many are invalid. + + If *all* entries in BackendRefs are invalid, and there are also no filters + specified in this route rule, *all* traffic which matches this rule MUST + receive a 500 status code. + + See the HTTPBackendRef definition for the rules about what makes a single + HTTPBackendRef invalid. + + When a HTTPBackendRef is invalid, 500 status codes MUST be returned for + requests that would have otherwise been routed to an invalid backend. If + multiple backends are specified, and some are invalid, the proportion of + requests that would otherwise have been routed to an invalid backend + MUST receive a 500 status code. + + For example, if two backends are specified with equal weights, and one is + invalid, 50 percent of traffic must receive a 500. Implementations may + choose how that 50 percent is determined. + + When a HTTPBackendRef refers to a Service that has no ready endpoints, + implementations SHOULD return a 503 for requests to that backend instead. + If an implementation chooses to do this, all of the above rules for 500 responses + MUST also apply for responses that return a 503. + + Support: Core for Kubernetes Service + + Support: Extended for Kubernetes ServiceImport + + Support: Implementation-specific for any other resource + + Support for weight: Core + items: + description: |- + HTTPBackendRef defines how a HTTPRoute forwards a HTTP request. + + Note that when a namespace different than the local namespace is specified, a + ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + properties: + filters: + description: |- + Filters defined at this level should be executed if and only if the + request is being forwarded to the backend defined here. + + Support: Implementation-specific (For broader support of filters, use the + Filters field in HTTPRouteRule.) + items: + description: |- + HTTPRouteFilter defines processing steps that must be completed during the + request or response lifecycle. HTTPRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + This filter can be used multiple times within the same rule. + + Support: Implementation-specific + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal + to denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be + specified in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + requestRedirect: + description: |- + RequestRedirect defines a schema for a filter that responds to the + request with an HTTP redirection. + + Support: Core + properties: + hostname: + description: |- + Hostname is the hostname to be used in the value of the `Location` + header in the response. + When empty, the hostname in the `Host` header of the request is used. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines parameters used to modify the path of the incoming request. + The modified path is then used to construct the `Location` header. When + empty, the request path is used as-is. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified + when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? + has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when + replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type + == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified + when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' + ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' + when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + port: + description: |- + Port is the port to be used in the value of the `Location` + header in the response. + + If no port is specified, the redirect port MUST be derived using the + following rules: + + * If redirect scheme is not-empty, the redirect port MUST be the well-known + port associated with the redirect scheme. Specifically "http" to port 80 + and "https" to port 443. If the redirect scheme does not have a + well-known port, the listener port of the Gateway SHOULD be used. + * If redirect scheme is empty, the redirect port MUST be the Gateway + Listener port. + + Implementations SHOULD NOT add the port number in the 'Location' + header in the following cases: + + * A Location header that will use HTTP (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 80. + * A Location header that will use HTTPS (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 443. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: |- + Scheme is the scheme to be used in the value of the `Location` header in + the response. When empty, the scheme of the request is used. + + Scheme redirects can affect the port of the redirect, for more information, + refer to the documentation for the port field of this filter. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Extended + enum: + - http + - https + type: string + statusCode: + default: 302 + description: |- + StatusCode is the HTTP status code to be used in response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Core + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations must support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by + specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` should be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: |- + URLRewrite defines a schema for a filter that modifies a request during forwarding. + + Support: Extended + properties: + hostname: + description: |- + Hostname is the value to be used to replace the Host header value during + forwarding. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines a path rewrite. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified + when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? + has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when + replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type + == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified + when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' + ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' + when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil + if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type + != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type + == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil + if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type + != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for + RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == + ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the + filter.type is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != + ''RequestRedirect'')' + - message: filter.requestRedirect must be specified + for RequestRedirect filter.type + rule: '!(!has(self.requestRedirect) && self.type == + ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type + is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite + filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for + ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') + && self.exists(f, f.type == ''URLRewrite''))' + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') + && self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() + <= 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() + <= 1 + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + filters: + description: |- + Filters define the filters that are applied to requests that match + this rule. + + Wherever possible, implementations SHOULD implement filters in the order + they are specified. + + Implementations MAY choose to implement this ordering strictly, rejecting + any combination or order of filters that cannot be supported. If implementations + choose a strict interpretation of filter ordering, they MUST clearly document + that behavior. + + To reject an invalid combination or order of filters, implementations SHOULD + consider the Route Rules with this configuration invalid. If all Route Rules + in a Route are invalid, the entire Route would be considered invalid. If only + a portion of Route Rules are invalid, implementations MUST set the + "PartiallyInvalid" condition for the Route. + + Conformance-levels at this level are defined based on the type of filter: + + - ALL core filters MUST be supported by all implementations. + - Implementers are encouraged to support extended filters. + - Implementation-specific custom filters have no API guarantees across + implementations. + + Specifying the same filter multiple times is not supported unless explicitly + indicated in the filter. + + All filters are expected to be compatible with each other except for the + URLRewrite and RequestRedirect filters, which may not be combined. If an + implementation cannot support other combinations of filters, they must clearly + document that limitation. In cases where incompatible or unsupported + filters are specified and cause the `Accepted` condition to be set to status + `False`, implementations may use the `IncompatibleFilters` reason to specify + this configuration error. + + Support: Core + items: + description: |- + HTTPRouteFilter defines processing steps that must be completed during the + request or response lifecycle. HTTPRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + This filter can be used multiple times within the same rule. + + Support: Implementation-specific + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal to + denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be specified + in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + requestRedirect: + description: |- + RequestRedirect defines a schema for a filter that responds to the + request with an HTTP redirection. + + Support: Core + properties: + hostname: + description: |- + Hostname is the hostname to be used in the value of the `Location` + header in the response. + When empty, the hostname in the `Host` header of the request is used. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines parameters used to modify the path of the incoming request. + The modified path is then used to construct the `Location` header. When + empty, the request path is used as-is. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when + type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) + : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath + is set + rule: 'has(self.replaceFullPath) ? self.type == + ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when + type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) + : true' + - message: type must be 'ReplacePrefixMatch' when + replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + port: + description: |- + Port is the port to be used in the value of the `Location` + header in the response. + + If no port is specified, the redirect port MUST be derived using the + following rules: + + * If redirect scheme is not-empty, the redirect port MUST be the well-known + port associated with the redirect scheme. Specifically "http" to port 80 + and "https" to port 443. If the redirect scheme does not have a + well-known port, the listener port of the Gateway SHOULD be used. + * If redirect scheme is empty, the redirect port MUST be the Gateway + Listener port. + + Implementations SHOULD NOT add the port number in the 'Location' + header in the following cases: + + * A Location header that will use HTTP (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 80. + * A Location header that will use HTTPS (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 443. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: |- + Scheme is the scheme to be used in the value of the `Location` header in + the response. When empty, the scheme of the request is used. + + Scheme redirects can affect the port of the redirect, for more information, + refer to the documentation for the port field of this filter. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Extended + enum: + - http + - https + type: string + statusCode: + default: 302 + description: |- + StatusCode is the HTTP status code to be used in response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Core + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations must support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by + specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` should be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: |- + URLRewrite defines a schema for a filter that modifies a request during forwarding. + + Support: Extended + properties: + hostname: + description: |- + Hostname is the value to be used to replace the Host header value during + forwarding. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines a path rewrite. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when + type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) + : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath + is set + rule: 'has(self.replaceFullPath) ? self.type == + ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when + type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) + : true' + - message: type must be 'ReplacePrefixMatch' when + replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the + filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != + ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == + ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the + filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != + ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror + filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the filter.type + is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')' + - message: filter.requestRedirect must be specified for RequestRedirect + filter.type + rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type + is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite + filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef + filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') && + self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() <= + 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() <= 1 + matches: + default: + - path: + type: PathPrefix + value: / + description: |- + Matches define conditions used for matching the rule against incoming + HTTP requests. Each match is independent, i.e. this rule will be matched + if **any** one of the matches is satisfied. + + For example, take the following matches configuration: + + ``` + matches: + - path: + value: "/foo" + headers: + - name: "version" + value: "v2" + - path: + value: "/v2/foo" + ``` + + For a request to match against this rule, a request must satisfy + EITHER of the two conditions: + + - path prefixed with `/foo` AND contains the header `version: v2` + - path prefix of `/v2/foo` + + See the documentation for HTTPRouteMatch on how to specify multiple + match conditions that should be ANDed together. + + If no matches are specified, the default is a prefix + path match on "/", which has the effect of matching every + HTTP request. + + Proxy or Load Balancer routing configuration generated from HTTPRoutes + MUST prioritize matches based on the following criteria, continuing on + ties. Across all rules specified on applicable Routes, precedence must be + given to the match having: + + * "Exact" path match. + * "Prefix" path match with largest number of characters. + * Method match. + * Largest number of header matches. + * Largest number of query param matches. + + Note: The precedence of RegularExpression path matches are implementation-specific. + + If ties still exist across multiple Routes, matching precedence MUST be + determined in order of the following criteria, continuing on ties: + + * The oldest Route based on creation timestamp. + * The Route appearing first in alphabetical order by + "{namespace}/{name}". + + If ties still exist within an HTTPRoute, matching precedence MUST be granted + to the FIRST matching rule (in list order) with a match meeting the above + criteria. + + When no rules matching a request have been successfully attached to the + parent a request is coming from, a HTTP 404 status code MUST be returned. + items: + description: "HTTPRouteMatch defines the predicate used to + match requests to a given\naction. Multiple match types + are ANDed together, i.e. the match will\nevaluate to true + only if all conditions are satisfied.\n\nFor example, the + match below will match a HTTP request only if its path\nstarts + with `/foo` AND it contains the `version: v1` header:\n\n```\nmatch:\n\n\tpath:\n\t + \ value: \"/foo\"\n\theaders:\n\t- name: \"version\"\n\t + \ value \"v1\"\n\n```" + properties: + headers: + description: |- + Headers specifies HTTP request header matchers. Multiple match values are + ANDed together, meaning, a request must match all the specified headers + to select the route. + items: + description: |- + HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request + headers. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + + When a header is repeated in an HTTP request, it is + implementation-specific behavior as to how this is represented. + Generally, proxies should follow the guidance from the RFC: + https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding + processing a repeated header, with special handling for "Set-Cookie". + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the header. + + Support: Core (Exact) + + Support: Implementation-specific (RegularExpression) + + Since RegularExpression HeaderMatchType has implementation-specific + conformance, implementations can support POSIX, PCRE or any other dialects + of regular expressions. Please read the implementation's documentation to + determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP Header to + be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: |- + Method specifies HTTP method matcher. + When specified, this route will be matched only if the request has the + specified method. + + Support: Extended + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: |- + Path specifies a HTTP request path matcher. If this field is not + specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: |- + Type specifies how to match against the path Value. + + Support: Core (Exact, PathPrefix) + + Support: Implementation-specific (RegularExpression) + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path to match against. + maxLength: 1024 + type: string + type: object + x-kubernetes-validations: + - message: value must be an absolute path and start with + '/' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.startsWith(''/'') + : true' + - message: must not contain '//' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''//'') + : true' + - message: must not contain '/./' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/./'') + : true' + - message: must not contain '/../' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/../'') + : true' + - message: must not contain '%2f' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2f'') + : true' + - message: must not contain '%2F' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2F'') + : true' + - message: must not contain '#' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''#'') + : true' + - message: must not end with '/..' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/..'') + : true' + - message: must not end with '/.' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/.'') + : true' + - message: type must be one of ['Exact', 'PathPrefix', + 'RegularExpression'] + rule: self.type in ['Exact','PathPrefix'] || self.type + == 'RegularExpression' + - message: must only contain valid characters (matching + ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$) + for types ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.matches(r"""^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$""") + : true' + queryParams: + description: |- + QueryParams specifies HTTP query parameter matchers. Multiple match + values are ANDed together, meaning, a request must match all the + specified query parameters to select the route. + + Support: Extended + items: + description: |- + HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP + query parameters. + properties: + name: + description: |- + Name is the name of the HTTP query param to be matched. This must be an + exact string match. (See + https://tools.ietf.org/html/rfc7230#section-2.7.3). + + If multiple entries specify equivalent query param names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent query param name MUST be ignored. + + If a query param is repeated in an HTTP request, the behavior is + purposely left undefined, since different data planes have different + capabilities. However, it is *recommended* that implementations should + match against the first value of the param if the data plane supports it, + as this behavior is expected in other load balancing contexts outside of + the Gateway API. + + Users SHOULD NOT route traffic based on repeated query params to guard + themselves against potential differences in the implementations. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the query parameter. + + Support: Extended (Exact) + + Support: Implementation-specific (RegularExpression) + + Since RegularExpression QueryParamMatchType has Implementation-specific + conformance, implementations can support POSIX, PCRE or any other + dialects of regular expressions. Please read the implementation's + documentation to determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP query param + to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 64 + type: array + timeouts: + description: |- + Timeouts defines the timeouts that can be configured for an HTTP request. + + Support: Extended + properties: + backendRequest: + description: |- + BackendRequest specifies a timeout for an individual request from the gateway + to a backend. This covers the time from when the request first starts being + sent from the gateway to when the full response has been received from the backend. + + Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout + completely. Implementations that cannot completely disable the timeout MUST + instead interpret the zero duration as the longest possible value to which + the timeout can be set. + + An entire client HTTP transaction with a gateway, covered by the Request timeout, + may result in more than one call from the gateway to the destination backend, + for example, if automatic retries are supported. + + The value of BackendRequest must be a Gateway API Duration string as defined by + GEP-2257. When this field is unspecified, its behavior is implementation-specific; + when specified, the value of BackendRequest must be no more than the value of the + Request timeout (since the Request timeout encompasses the BackendRequest timeout). + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + request: + description: |- + Request specifies the maximum duration for a gateway to respond to an HTTP request. + If the gateway has not been able to respond before this deadline is met, the gateway + MUST return a timeout error. + + For example, setting the `rules.timeouts.request` field to the value `10s` in an + `HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds + to complete. + + Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout + completely. Implementations that cannot completely disable the timeout MUST + instead interpret the zero duration as the longest possible value to which + the timeout can be set. + + This timeout is intended to cover as close to the whole request-response transaction + as possible although an implementation MAY choose to start the timeout after the entire + request stream has been received instead of immediately after the transaction is + initiated by the client. + + The value of Request is a Gateway API Duration string as defined by GEP-2257. When this + field is unspecified, request timeout behavior is implementation-specific. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + x-kubernetes-validations: + - message: backendRequest timeout cannot be longer than request + timeout + rule: '!(has(self.request) && has(self.backendRequest) && + duration(self.request) != duration(''0s'') && duration(self.backendRequest) + > duration(self.request))' + type: object + x-kubernetes-validations: + - message: RequestRedirect filter must not be used together with + backendRefs + rule: '(has(self.backendRefs) && size(self.backendRefs) > 0) ? + (!has(self.filters) || self.filters.all(f, !has(f.requestRedirect))): + true' + - message: When using RequestRedirect filter with path.replacePrefixMatch, + exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.requestRedirect) + && has(f.requestRedirect.path) && f.requestRedirect.path.type + == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) + ? ((size(self.matches) != 1 || !has(self.matches[0].path) || + self.matches[0].path.type != ''PathPrefix'') ? false : true) + : true' + - message: When using URLRewrite filter with path.replacePrefixMatch, + exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.urlRewrite) + && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' + && has(f.urlRewrite.path.replacePrefixMatch))) ? ((size(self.matches) + != 1 || !has(self.matches[0].path) || self.matches[0].path.type + != ''PathPrefix'') ? false : true) : true' + - message: Within backendRefs, when using RequestRedirect filter + with path.replacePrefixMatch, exactly one PathPrefix match must + be specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, + (has(b.filters) && b.filters.exists_one(f, has(f.requestRedirect) + && has(f.requestRedirect.path) && f.requestRedirect.path.type + == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) + )) ? ((size(self.matches) != 1 || !has(self.matches[0].path) + || self.matches[0].path.type != ''PathPrefix'') ? false : true) + : true' + - message: Within backendRefs, When using URLRewrite filter with + path.replacePrefixMatch, exactly one PathPrefix match must be + specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, + (has(b.filters) && b.filters.exists_one(f, has(f.urlRewrite) + && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' + && has(f.urlRewrite.path.replacePrefixMatch))) )) ? ((size(self.matches) + != 1 || !has(self.matches[0].path) || self.matches[0].path.type + != ''PathPrefix'') ? false : true) : true' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: While 16 rules and 64 matches per rule are allowed, the + total number of matches across all rules in a route must be less + than 128 + rule: '(self.size() > 0 ? self[0].matches.size() : 0) + (self.size() + > 1 ? self[1].matches.size() : 0) + (self.size() > 2 ? self[2].matches.size() + : 0) + (self.size() > 3 ? self[3].matches.size() : 0) + (self.size() + > 4 ? self[4].matches.size() : 0) + (self.size() > 5 ? self[5].matches.size() + : 0) + (self.size() > 6 ? self[6].matches.size() : 0) + (self.size() + > 7 ? self[7].matches.size() : 0) + (self.size() > 8 ? self[8].matches.size() + : 0) + (self.size() > 9 ? self[9].matches.size() : 0) + (self.size() + > 10 ? self[10].matches.size() : 0) + (self.size() > 11 ? self[11].matches.size() + : 0) + (self.size() > 12 ? self[12].matches.size() : 0) + (self.size() + > 13 ? self[13].matches.size() : 0) + (self.size() > 14 ? self[14].matches.size() + : 0) + (self.size() > 15 ? self[15].matches.size() : 0) <= 128' + type: object + status: + description: Status defines the current state of HTTPRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# +# config/crd/standard/gateway.networking.k8s.io_referencegrants.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: standard + creationTimestamp: null + name: referencegrants.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: ReferenceGrant + listKind: ReferenceGrantList + plural: referencegrants + shortNames: + - refgrant + singular: referencegrant + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: |- + ReferenceGrant identifies kinds of resources in other namespaces that are + trusted to reference the specified kinds of resources in the same namespace + as the policy. + + Each ReferenceGrant can be used to represent a unique trust relationship. + Additional Reference Grants can be used to add to the set of trusted + sources of inbound references for the namespace they are defined within. + + All cross-namespace references in Gateway API (with the exception of cross-namespace + Gateway-route attachment) require a ReferenceGrant. + + ReferenceGrant is a form of runtime verification allowing users to assert + which cross-namespace object references are permitted. Implementations that + support ReferenceGrant MUST NOT permit cross-namespace references which have + no grant, and MUST respond to the removal of a grant by revoking the access + that the grant allowed. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of ReferenceGrant. + properties: + from: + description: |- + From describes the trusted namespaces and kinds that can reference the + resources described in "To". Each entry in this list MUST be considered + to be an additional place that references can be valid from, or to put + this another way, entries MUST be combined using OR. + + Support: Core + items: + description: ReferenceGrantFrom describes trusted namespaces and + kinds. + properties: + group: + description: |- + Group is the group of the referent. + When empty, the Kubernetes core API group is inferred. + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: |- + Kind is the kind of the referent. Although implementations may support + additional resources, the following types are part of the "Core" + support level for this field. + + When used to permit a SecretObjectReference: + + * Gateway + + When used to permit a BackendObjectReference: + + * GRPCRoute + * HTTPRoute + * TCPRoute + * TLSRoute + * UDPRoute + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + namespace: + description: |- + Namespace is the namespace of the referent. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - namespace + type: object + maxItems: 16 + minItems: 1 + type: array + to: + description: |- + To describes the resources that may be referenced by the resources + described in "From". Each entry in this list MUST be considered to be an + additional place that references can be valid to, or to put this another + way, entries MUST be combined using OR. + + Support: Core + items: + description: |- + ReferenceGrantTo describes what Kinds are allowed as targets of the + references. + properties: + group: + description: |- + Group is the group of the referent. + When empty, the Kubernetes core API group is inferred. + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: |- + Kind is the kind of the referent. Although implementations may support + additional resources, the following types are part of the "Core" + support level for this field: + + * Secret when used to permit a SecretObjectReference + * Service when used to permit a BackendObjectReference + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. When unspecified, this policy + refers to all resources of the specified Group and Kind in the local + namespace. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - from + - to + type: object + type: object + served: true + storage: true + subresources: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +{{- end }} diff --git a/charts/gateway-crds-helm/values.tmpl.yaml b/charts/gateway-crds-helm/values.tmpl.yaml index d1433de319..f6f17ccb58 100644 --- a/charts/gateway-crds-helm/values.tmpl.yaml +++ b/charts/gateway-crds-helm/values.tmpl.yaml @@ -1,5 +1,6 @@ crds: gatewayAPI: enabled: false + channel: experimental # Available options: standard, experimental envoyGateway: enabled: false diff --git a/charts/gateway-crds-helm/values.yaml b/charts/gateway-crds-helm/values.yaml index d1433de319..f6f17ccb58 100644 --- a/charts/gateway-crds-helm/values.yaml +++ b/charts/gateway-crds-helm/values.yaml @@ -1,5 +1,6 @@ crds: gatewayAPI: enabled: false + channel: experimental # Available options: standard, experimental envoyGateway: enabled: false diff --git a/release-notes/current.yaml b/release-notes/current.yaml index ea823a6d3f..bcced8623e 100644 --- a/release-notes/current.yaml +++ b/release-notes/current.yaml @@ -8,6 +8,7 @@ security updates: | # New features or capabilities added in this release. new features: | + Added support for optionally installing the Gateway API standard channel CRDs through the gateway-crds-helm chart. bug fixes: | Fix reference grant from SecurityPolicy to referenced remoteJWKS backend not respected. diff --git a/site/content/en/latest/install/gateway-crds-helm-api.md b/site/content/en/latest/install/gateway-crds-helm-api.md index c2f3d84882..febf9f9bd5 100644 --- a/site/content/en/latest/install/gateway-crds-helm-api.md +++ b/site/content/en/latest/install/gateway-crds-helm-api.md @@ -11,5 +11,6 @@ A Helm chart for Kubernetes | Key | Type | Default | Description | |-----|------|---------|-------------| | crds.envoyGateway.enabled | bool | `false` | | +| crds.gatewayAPI.channel | string | `"experimental"` | | | crds.gatewayAPI.enabled | bool | `false` | | diff --git a/test/helm/gateway-crds-helm/all.out.yaml b/test/helm/gateway-crds-helm/all.out.yaml index 8d3a3a2403..be47c7f6c9 100644 --- a/test/helm/gateway-crds-helm/all.out.yaml +++ b/test/helm/gateway-crds-helm/all.out.yaml @@ -1,5 +1,5 @@ --- -# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml # # config/crd/experimental/gateway.networking.k8s.io_backendtlspolicies.yaml # @@ -651,7 +651,7 @@ status: conditions: null storedVersions: null --- -# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml # # config/crd/experimental/gateway.networking.k8s.io_gatewayclasses.yaml # @@ -1172,7 +1172,7 @@ status: conditions: null storedVersions: null --- -# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml # # config/crd/experimental/gateway.networking.k8s.io_gateways.yaml # @@ -3881,7 +3881,7 @@ status: conditions: null storedVersions: null --- -# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml # # config/crd/experimental/gateway.networking.k8s.io_grpcroutes.yaml # @@ -6103,7 +6103,7 @@ status: conditions: null storedVersions: null --- -# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml # # config/crd/experimental/gateway.networking.k8s.io_httproutes.yaml # @@ -13386,7 +13386,7 @@ status: conditions: null storedVersions: null --- -# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml # # config/crd/experimental/gateway.networking.k8s.io_referencegrants.yaml # @@ -13580,7 +13580,7 @@ status: conditions: null storedVersions: null --- -# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml # # config/crd/experimental/gateway.networking.k8s.io_tcproutes.yaml # @@ -14317,7 +14317,7 @@ status: conditions: null storedVersions: null --- -# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml # # config/crd/experimental/gateway.networking.k8s.io_tlsroutes.yaml # @@ -15117,7 +15117,7 @@ status: conditions: null storedVersions: null --- -# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml # # config/crd/experimental/gateway.networking.k8s.io_udproutes.yaml # @@ -15854,7 +15854,7 @@ status: conditions: null storedVersions: null --- -# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml # # config/crd/experimental/gateway.networking.x-k8s.io_xbackendtrafficpolicies.yaml # @@ -16464,7 +16464,7 @@ status: conditions: null storedVersions: null --- -# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml # # config/crd/experimental/gateway.networking.x-k8s.io_xlistenersets.yaml # @@ -43462,7 +43462,7 @@ spec: subresources: status: {} --- -# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml # Copyright 2025 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test/helm/gateway-crds-helm/gateway-api-crds.out.yaml b/test/helm/gateway-crds-helm/gateway-api-crds.out.yaml index c606e60a0b..5a0b38d2cb 100644 --- a/test/helm/gateway-crds-helm/gateway-api-crds.out.yaml +++ b/test/helm/gateway-crds-helm/gateway-api-crds.out.yaml @@ -1,5 +1,5 @@ --- -# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml # # config/crd/experimental/gateway.networking.k8s.io_backendtlspolicies.yaml # @@ -651,7 +651,7 @@ status: conditions: null storedVersions: null --- -# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml # # config/crd/experimental/gateway.networking.k8s.io_gatewayclasses.yaml # @@ -1172,7 +1172,7 @@ status: conditions: null storedVersions: null --- -# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml # # config/crd/experimental/gateway.networking.k8s.io_gateways.yaml # @@ -3881,7 +3881,7 @@ status: conditions: null storedVersions: null --- -# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml # # config/crd/experimental/gateway.networking.k8s.io_grpcroutes.yaml # @@ -6103,7 +6103,7 @@ status: conditions: null storedVersions: null --- -# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml # # config/crd/experimental/gateway.networking.k8s.io_httproutes.yaml # @@ -13386,7 +13386,7 @@ status: conditions: null storedVersions: null --- -# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml # # config/crd/experimental/gateway.networking.k8s.io_referencegrants.yaml # @@ -13580,7 +13580,7 @@ status: conditions: null storedVersions: null --- -# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml # # config/crd/experimental/gateway.networking.k8s.io_tcproutes.yaml # @@ -14317,7 +14317,7 @@ status: conditions: null storedVersions: null --- -# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml # # config/crd/experimental/gateway.networking.k8s.io_tlsroutes.yaml # @@ -15117,7 +15117,7 @@ status: conditions: null storedVersions: null --- -# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml # # config/crd/experimental/gateway.networking.k8s.io_udproutes.yaml # @@ -15854,7 +15854,7 @@ status: conditions: null storedVersions: null --- -# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml # # config/crd/experimental/gateway.networking.x-k8s.io_xbackendtrafficpolicies.yaml # @@ -16464,7 +16464,7 @@ status: conditions: null storedVersions: null --- -# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml # # config/crd/experimental/gateway.networking.x-k8s.io_xlistenersets.yaml # @@ -17311,7 +17311,7 @@ status: conditions: null storedVersions: null --- -# Source: gateway-crds-helm/templates/gatewayapi-crds.yaml +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml # Copyright 2025 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test/helm/gateway-crds-helm/gateway-api-experimental-crds.in.yaml b/test/helm/gateway-crds-helm/gateway-api-experimental-crds.in.yaml new file mode 100644 index 0000000000..e8e33fbdb6 --- /dev/null +++ b/test/helm/gateway-crds-helm/gateway-api-experimental-crds.in.yaml @@ -0,0 +1,6 @@ +crds: + gatewayAPI: + enabled: true + channel: experimental + envoyGateway: + enabled: false diff --git a/test/helm/gateway-crds-helm/gateway-api-experimental-crds.out.yaml b/test/helm/gateway-crds-helm/gateway-api-experimental-crds.out.yaml new file mode 100644 index 0000000000..5a0b38d2cb --- /dev/null +++ b/test/helm/gateway-crds-helm/gateway-api-experimental-crds.out.yaml @@ -0,0 +1,17331 @@ +--- +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_backendtlspolicies.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + labels: + gateway.networking.k8s.io/policy: Direct + name: backendtlspolicies.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: BackendTLSPolicy + listKind: BackendTLSPolicyList + plural: backendtlspolicies + shortNames: + - btlspolicy + singular: backendtlspolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha3 + schema: + openAPIV3Schema: + description: |- + BackendTLSPolicy provides a way to configure how a Gateway + connects to a Backend via TLS. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of BackendTLSPolicy. + properties: + options: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Options are a list of key/value pairs to enable extended TLS + configuration for each implementation. For example, configuring the + minimum TLS version or supported cipher suites. + + A set of common keys MAY be defined by the API in the future. To avoid + any ambiguity, implementation-specific definitions MUST use + domain-prefixed names, such as `example.com/my-custom-option`. + Un-prefixed names are reserved for key names defined by Gateway API. + + Support: Implementation-specific + maxProperties: 16 + type: object + targetRefs: + description: |- + TargetRefs identifies an API object to apply the policy to. + Only Services have Extended support. Implementations MAY support + additional objects, with Implementation Specific support. + Note that this config applies to the entire referenced resource + by default, but this default may change in the future to provide + a more granular application of the policy. + + TargetRefs must be _distinct_. This means either that: + + * They select different targets. If this is the case, then targetRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, and `name` must + be unique across all targetRef entries in the BackendTLSPolicy. + * They select different sectionNames in the same target. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + items: + description: |- + LocalPolicyTargetReferenceWithSectionName identifies an API object to apply a + direct policy to. This should be used as part of Policy resources that can + target single resources. For more information on how this policy attachment + mode works, and a sample Policy resource, refer to the policy attachment + documentation for Gateway API. + + Note: This should only be used for direct policy attachment when references + to SectionName are actually needed. In all other cases, + LocalPolicyTargetReference should be used. + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + sectionName: + description: |- + SectionName is the name of a section within the target resource. When + unspecified, this targetRef targets the entire resource. In the following + resources, SectionName is interpreted as the following: + + * Gateway: Listener name + * HTTPRoute: HTTPRouteRule name + * Service: Port name + + If a SectionName is specified, but does not exist on the targeted object, + the Policy must fail to attach, and the policy implementation should record + a `ResolvedRefs` or similar Condition in the Policy's status. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - group + - kind + - name + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-validations: + - message: sectionName must be specified when targetRefs includes + 2 or more references to the same target + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name ? ((!has(p1.sectionName) || p1.sectionName + == '''') == (!has(p2.sectionName) || p2.sectionName == '''')) + : true))' + - message: sectionName must be unique when targetRefs includes 2 or + more references to the same target + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.sectionName) || + p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || (has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)))) + validation: + description: Validation contains backend TLS validation configuration. + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to Kubernetes objects that + contain a PEM-encoded TLS CA certificate bundle, which is used to + validate a TLS handshake between the Gateway and backend Pod. + + If CACertificateRefs is empty or unspecified, then WellKnownCACertificates must be + specified. Only one of CACertificateRefs or WellKnownCACertificates may be specified, + not both. If CACertificateRefs is empty or unspecified, the configuration for + WellKnownCACertificates MUST be honored instead if supported by the implementation. + + References to a resource in a different namespace are invalid for the + moment, although we will revisit this in the future. + + A single CACertificateRef to a Kubernetes ConfigMap kind has "Core" support. + Implementations MAY choose to support attaching multiple certificates to + a backend, but this behavior is implementation-specific. + + Support: Core - An optional single reference to a Kubernetes ConfigMap, + with the CA certificate in a key named `ca.crt`. + + Support: Implementation-specific (More than one reference, or other kinds + of resources). + items: + description: |- + LocalObjectReference identifies an API object within the namespace of the + referrer. + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example "HTTPRoute" + or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + maxItems: 8 + type: array + hostname: + description: |- + Hostname is used for two purposes in the connection between Gateways and + backends: + + 1. Hostname MUST be used as the SNI to connect to the backend (RFC 6066). + 2. Hostname MUST be used for authentication and MUST match the certificate served by the matching backend, unless SubjectAltNames is specified. + authentication and MUST match the certificate served by the matching + backend. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + subjectAltNames: + description: |- + SubjectAltNames contains one or more Subject Alternative Names. + When specified the certificate served from the backend MUST + have at least one Subject Alternate Name matching one of the specified SubjectAltNames. + + Support: Extended + items: + description: SubjectAltName represents Subject Alternative Name. + properties: + hostname: + description: |- + Hostname contains Subject Alternative Name specified in DNS name format. + Required when Type is set to Hostname, ignored otherwise. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: + description: |- + Type determines the format of the Subject Alternative Name. Always required. + + Support: Core + enum: + - Hostname + - URI + type: string + uri: + description: |- + URI contains Subject Alternative Name specified in a full URI format. + It MUST include both a scheme (e.g., "http" or "ftp") and a scheme-specific-part. + Common values include SPIFFE IDs like "spiffe://mycluster.example.com/ns/myns/sa/svc1sa". + Required when Type is set to URI, ignored otherwise. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))? + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: SubjectAltName element must contain Hostname, if + Type is set to Hostname + rule: '!(self.type == "Hostname" && (!has(self.hostname) || + self.hostname == ""))' + - message: SubjectAltName element must not contain Hostname, + if Type is not set to Hostname + rule: '!(self.type != "Hostname" && has(self.hostname) && + self.hostname != "")' + - message: SubjectAltName element must contain URI, if Type + is set to URI + rule: '!(self.type == "URI" && (!has(self.uri) || self.uri + == ""))' + - message: SubjectAltName element must not contain URI, if Type + is not set to URI + rule: '!(self.type != "URI" && has(self.uri) && self.uri != + "")' + maxItems: 5 + type: array + wellKnownCACertificates: + description: |- + WellKnownCACertificates specifies whether system CA certificates may be used in + the TLS handshake between the gateway and backend pod. + + If WellKnownCACertificates is unspecified or empty (""), then CACertificateRefs + must be specified with at least one entry for a valid configuration. Only one of + CACertificateRefs or WellKnownCACertificates may be specified, not both. If an + implementation does not support the WellKnownCACertificates field or the value + supplied is not supported, the Status Conditions on the Policy MUST be + updated to include an Accepted: False Condition with Reason: Invalid. + + Support: Implementation-specific + enum: + - System + type: string + required: + - hostname + type: object + x-kubernetes-validations: + - message: must not contain both CACertificateRefs and WellKnownCACertificates + rule: '!(has(self.caCertificateRefs) && size(self.caCertificateRefs) + > 0 && has(self.wellKnownCACertificates) && self.wellKnownCACertificates + != "")' + - message: must specify either CACertificateRefs or WellKnownCACertificates + rule: (has(self.caCertificateRefs) && size(self.caCertificateRefs) + > 0 || has(self.wellKnownCACertificates) && self.wellKnownCACertificates + != "") + required: + - targetRefs + - validation + type: object + status: + description: Status defines the current state of BackendTLSPolicy. + properties: + ancestors: + description: |- + Ancestors is a list of ancestor resources (usually Gateways) that are + associated with the policy, and the status of the policy with respect to + each ancestor. When this policy attaches to a parent, the controller that + manages the parent and the ancestors MUST add an entry to this list when + the controller first sees the policy and SHOULD update the entry as + appropriate when the relevant ancestor is modified. + + Note that choosing the relevant ancestor is left to the Policy designers; + an important part of Policy design is designing the right object level at + which to namespace this status. + + Note also that implementations MUST ONLY populate ancestor status for + the Ancestor resources they are responsible for. Implementations MUST + use the ControllerName field to uniquely identify the entries in this list + that they are responsible for. + + Note that to achieve this, the list of PolicyAncestorStatus structs + MUST be treated as a map with a composite key, made up of the AncestorRef + and ControllerName fields combined. + + A maximum of 16 ancestors will be represented in this list. An empty list + means the Policy is not relevant for any ancestors. + + If this slice is full, implementations MUST NOT add further entries. + Instead they MUST consider the policy unimplementable and signal that + on any related resources such as the ancestor that would be referenced + here. For example, if this list was full on BackendTLSPolicy, no + additional Gateways would be able to reference the Service targeted by + the BackendTLSPolicy. + items: + description: |- + PolicyAncestorStatus describes the status of a route with respect to an + associated Ancestor. + + Ancestors refer to objects that are either the Target of a policy or above it + in terms of object hierarchy. For example, if a policy targets a Service, the + Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and + the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most + useful object to place Policy status on, so we recommend that implementations + SHOULD use Gateway as the PolicyAncestorStatus object unless the designers + have a _very_ good reason otherwise. + + In the context of policy attachment, the Ancestor is used to distinguish which + resource results in a distinct application of this policy. For example, if a policy + targets a Service, it may have a distinct result per attached Gateway. + + Policies targeting the same resource may have different effects depending on the + ancestors of those resources. For example, different Gateways targeting the same + Service may have different capabilities, especially if they have different underlying + implementations. + + For example, in BackendTLSPolicy, the Policy attaches to a Service that is + used as a backend in a HTTPRoute that is itself attached to a Gateway. + In this case, the relevant object for status is the Gateway, and that is the + ancestor object referred to in this status. + + Note that a parent is also an ancestor, so for objects where the parent is the + relevant object for status, this struct SHOULD still be used. + + This struct is intended to be used in a slice that's effectively a map, + with a composite key made up of the AncestorRef and the ControllerName. + properties: + ancestorRef: + description: |- + AncestorRef corresponds with a ParentRef in the spec that this + PolicyAncestorStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: Conditions describes the status of the Policy with + respect to the given Ancestor. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - controllerName + type: object + maxItems: 16 + type: array + required: + - ancestors + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_gatewayclasses.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: gatewayclasses.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: GatewayClass + listKind: GatewayClassList + plural: gatewayclasses + shortNames: + - gc + singular: gatewayclass + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.controllerName + name: Controller + type: string + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.description + name: Description + priority: 1 + type: string + name: v1 + schema: + openAPIV3Schema: + description: |- + GatewayClass describes a class of Gateways available to the user for creating + Gateway resources. + + 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. This recommendation is intended to + limit the blast radius of changes to GatewayClass or associated parameters. + If implementations choose to propagate GatewayClass changes to existing + Gateways, that MUST be clearly documented by the implementation. + + Whenever one or more Gateways are using a GatewayClass, implementations SHOULD + add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the + associated GatewayClass. This ensures that a GatewayClass associated with a + Gateway is not deleted while in use. + + GatewayClass is a Cluster level resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClass. + properties: + controllerName: + description: |- + ControllerName is the name of the controller that is managing Gateways of + this class. The value of this field MUST be a domain prefixed path. + + Example: "example.net/gateway-controller". + + This field is not mutable and cannot be empty. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + description: + description: Description helps describe a GatewayClass with more details. + maxLength: 64 + type: string + parametersRef: + description: |- + ParametersRef is a reference to a resource that contains the configuration + parameters corresponding to the GatewayClass. This is optional if the + controller does not require any additional configuration. + + ParametersRef can reference a standard Kubernetes resource, i.e. ConfigMap, + or an implementation-specific custom resource. The resource can be + cluster-scoped or namespace-scoped. + + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the GatewayClass SHOULD be + rejected with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + + A Gateway for this GatewayClass may provide its own `parametersRef`. When both are specified, + the merging behavior is implementation specific. + It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway. + + Support: Implementation-specific + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. + This field is required when referring to a Namespace-scoped resource and + MUST be unset when referring to a Cluster-scoped resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + required: + - controllerName + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: |- + Status defines the current state of GatewayClass. + + Implementations MUST populate status on all GatewayClass resources which + specify their controller name. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: |- + Conditions is the current status from the controller for + this GatewayClass. + + Controllers should prefer to publish conditions using values + of GatewayClassConditionType for the type of each Condition. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + supportedFeatures: + description: |- + SupportedFeatures is the set of features the GatewayClass support. + It MUST be sorted in ascending alphabetical order by the Name key. + items: + properties: + name: + description: |- + FeatureName is used to describe distinct features that are covered by + conformance tests. + type: string + required: + - name + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.controllerName + name: Controller + type: string + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.description + name: Description + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: |- + GatewayClass describes a class of Gateways available to the user for creating + Gateway resources. + + 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. This recommendation is intended to + limit the blast radius of changes to GatewayClass or associated parameters. + If implementations choose to propagate GatewayClass changes to existing + Gateways, that MUST be clearly documented by the implementation. + + Whenever one or more Gateways are using a GatewayClass, implementations SHOULD + add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the + associated GatewayClass. This ensures that a GatewayClass associated with a + Gateway is not deleted while in use. + + GatewayClass is a Cluster level resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClass. + properties: + controllerName: + description: |- + ControllerName is the name of the controller that is managing Gateways of + this class. The value of this field MUST be a domain prefixed path. + + Example: "example.net/gateway-controller". + + This field is not mutable and cannot be empty. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + description: + description: Description helps describe a GatewayClass with more details. + maxLength: 64 + type: string + parametersRef: + description: |- + ParametersRef is a reference to a resource that contains the configuration + parameters corresponding to the GatewayClass. This is optional if the + controller does not require any additional configuration. + + ParametersRef can reference a standard Kubernetes resource, i.e. ConfigMap, + or an implementation-specific custom resource. The resource can be + cluster-scoped or namespace-scoped. + + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the GatewayClass SHOULD be + rejected with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + + A Gateway for this GatewayClass may provide its own `parametersRef`. When both are specified, + the merging behavior is implementation specific. + It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway. + + Support: Implementation-specific + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. + This field is required when referring to a Namespace-scoped resource and + MUST be unset when referring to a Cluster-scoped resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + required: + - controllerName + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: |- + Status defines the current state of GatewayClass. + + Implementations MUST populate status on all GatewayClass resources which + specify their controller name. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: |- + Conditions is the current status from the controller for + this GatewayClass. + + Controllers should prefer to publish conditions using values + of GatewayClassConditionType for the type of each Condition. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + supportedFeatures: + description: |- + SupportedFeatures is the set of features the GatewayClass support. + It MUST be sorted in ascending alphabetical order by the Name key. + items: + properties: + name: + description: |- + FeatureName is used to describe distinct features that are covered by + conformance tests. + type: string + required: + - name + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_gateways.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: gateways.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: Gateway + listKind: GatewayList + plural: gateways + shortNames: + - gtw + singular: gateway + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.gatewayClassName + name: Class + type: string + - jsonPath: .status.addresses[*].value + name: Address + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + Gateway represents an instance of a service-traffic handling infrastructure + by binding Listeners to a set of IP addresses. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of Gateway. + properties: + addresses: + description: |- + Addresses requested for this Gateway. This is optional and behavior can + depend on the implementation. If a value is set in the spec and the + requested address is invalid or unavailable, the implementation MUST + indicate this in the associated entry in GatewayStatus.Addresses. + + The Addresses field represents a request for the address(es) on the + "outside of the Gateway", that traffic bound for this Gateway will use. + This could be the IP address or hostname of an external load balancer or + other networking infrastructure, or some other address that traffic will + be sent to. + + If no Addresses are specified, the implementation MAY schedule the + Gateway in an implementation-specific manner, assigning an appropriate + set of Addresses. + + The implementation MUST bind all Listeners to every GatewayAddress that + it assigns to the Gateway and add a corresponding entry in + GatewayStatus.Addresses. + + Support: Extended + items: + description: GatewaySpecAddress describes an address that can be + bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: |- + When a value is unspecified, an implementation SHOULD automatically + assign an address matching the requested type if possible. + + If an implementation does not support an empty value, they MUST set the + "Programmed" condition in status to False with a reason of "AddressNotAssigned". + + Examples: `1.2.3.4`, `128::1`, `my-ip-address`. + maxLength: 253 + type: string + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching + ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + true' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: IPAddress values must be unique + rule: 'self.all(a1, a1.type == ''IPAddress'' ? self.exists_one(a2, + a2.type == a1.type && a2.value == a1.value) : true )' + - message: Hostname values must be unique + rule: 'self.all(a1, a1.type == ''Hostname'' ? self.exists_one(a2, + a2.type == a1.type && a2.value == a1.value) : true )' + allowedListeners: + description: |- + AllowedListeners defines which ListenerSets can be attached to this Gateway. + While this feature is experimental, the default value is to allow no ListenerSets. + properties: + namespaces: + default: + from: None + description: |- + Namespaces defines which namespaces ListenerSets can be attached to this Gateway. + While this feature is experimental, the default value is to allow no ListenerSets. + properties: + from: + default: None + description: |- + From indicates where ListenerSets can attach to this Gateway. Possible + values are: + + * Same: Only ListenerSets in the same namespace may be attached to this Gateway. + * Selector: ListenerSets in namespaces selected by the selector may be attached to this Gateway. + * All: ListenerSets in all namespaces may be attached to this Gateway. + * None: Only listeners defined in the Gateway's spec are allowed + + While this feature is experimental, the default value None + enum: + - All + - Selector + - Same + - None + type: string + selector: + description: |- + Selector must be specified when From is set to "Selector". In that case, + only ListenerSets in Namespaces matching this Selector will be selected by this + Gateway. This field is ignored for other values of "From". + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + type: object + type: object + backendTLS: + description: |- + BackendTLS configures TLS settings for when this Gateway is connecting to + backends with TLS. + + Support: Core + properties: + clientCertificateRef: + description: |- + ClientCertificateRef is a reference to an object that contains a Client + Certificate and the associated private key. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + + ClientCertificateRef can reference to standard Kubernetes resources, i.e. + Secret, or implementation-specific custom resources. + + This setting can be overridden on the service level by use of BackendTLSPolicy. + + Support: Core + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + type: object + gatewayClassName: + description: |- + GatewayClassName used for this Gateway. This is the name of a + GatewayClass resource. + maxLength: 253 + minLength: 1 + type: string + infrastructure: + description: |- + Infrastructure defines infrastructure level attributes about this Gateway instance. + + Support: Extended + properties: + annotations: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Annotations that SHOULD be applied to any resources created in response to this Gateway. + + For implementations creating other Kubernetes objects, this should be the `metadata.annotations` field on resources. + For other implementations, this refers to any relevant (implementation specific) "annotations" concepts. + + An implementation may chose to add additional implementation-specific annotations as they see fit. + + Support: Extended + maxProperties: 8 + type: object + x-kubernetes-validations: + - message: Annotation keys must be in the form of an optional + DNS subdomain prefix followed by a required name segment of + up to 63 characters. + rule: self.all(key, key.matches(r"""^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9]$""")) + - message: If specified, the annotation key's prefix must be a + DNS subdomain not longer than 253 characters in total. + rule: self.all(key, key.split("/")[0].size() < 253) + labels: + additionalProperties: + description: |- + LabelValue is the value of a label in the Gateway API. This is used for validation + of maps such as Gateway infrastructure labels. This matches the Kubernetes + label validation rules: + * must be 63 characters or less (can be empty), + * unless empty, must begin and end with an alphanumeric character ([a-z0-9A-Z]), + * could contain dashes (-), underscores (_), dots (.), and alphanumerics between. + + Valid values include: + + * MyValue + * my.name + * 123-my-value + maxLength: 63 + minLength: 0 + pattern: ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$ + type: string + description: |- + Labels that SHOULD be applied to any resources created in response to this Gateway. + + For implementations creating other Kubernetes objects, this should be the `metadata.labels` field on resources. + For other implementations, this refers to any relevant (implementation specific) "labels" concepts. + + An implementation may chose to add additional implementation-specific labels as they see fit. + + If an implementation maps these labels to Pods, or any other resource that would need to be recreated when labels + change, it SHOULD clearly warn about this behavior in documentation. + + Support: Extended + maxProperties: 8 + type: object + x-kubernetes-validations: + - message: Label keys must be in the form of an optional DNS subdomain + prefix followed by a required name segment of up to 63 characters. + rule: self.all(key, key.matches(r"""^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9]$""")) + - message: If specified, the label key's prefix must be a DNS + subdomain not longer than 253 characters in total. + rule: self.all(key, key.split("/")[0].size() < 253) + parametersRef: + description: |- + ParametersRef is a reference to a resource that contains the configuration + parameters corresponding to the Gateway. This is optional if the + controller does not require any additional configuration. + + This follows the same semantics as GatewayClass's `parametersRef`, but on a per-Gateway basis + + The Gateway's GatewayClass may provide its own `parametersRef`. When both are specified, + the merging behavior is implementation specific. + It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway. + + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the Gateway SHOULD be + rejected with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + + Support: Implementation-specific + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + type: object + listeners: + description: |- + Listeners associated with this Gateway. Listeners define + logical endpoints that are bound on this Gateway's addresses. + At least one Listener MUST be specified. + + ## Distinct Listeners + + Each Listener in a set of Listeners (for example, in a single Gateway) + MUST be _distinct_, in that a traffic flow MUST be able to be assigned to + exactly one listener. (This section uses "set of Listeners" rather than + "Listeners in a single Gateway" because implementations MAY merge configuration + from multiple Gateways onto a single data plane, and these rules _also_ + apply in that case). + + Practically, this means that each listener in a set MUST have a unique + combination of Port, Protocol, and, if supported by the protocol, Hostname. + + Some combinations of port, protocol, and TLS settings are considered + Core support and MUST be supported by implementations based on the objects + they support: + + HTTPRoute + + 1. HTTPRoute, Port: 80, Protocol: HTTP + 2. HTTPRoute, Port: 443, Protocol: HTTPS, TLS Mode: Terminate, TLS keypair provided + + TLSRoute + + 1. TLSRoute, Port: 443, Protocol: TLS, TLS Mode: Passthrough + + "Distinct" Listeners have the following property: + + **The implementation can match inbound requests to a single distinct + Listener**. + + When multiple Listeners share values for fields (for + example, two Listeners with the same Port value), the implementation + can match requests to only one of the Listeners using other + Listener fields. + + When multiple listeners have the same value for the Protocol field, then + each of the Listeners with matching Protocol values MUST have different + values for other fields. + + The set of fields that MUST be different for a Listener differs per protocol. + The following rules define the rules for what fields MUST be considered for + Listeners to be distinct with each protocol currently defined in the + Gateway API spec. + + The set of listeners that all share a protocol value MUST have _different_ + values for _at least one_ of these fields to be distinct: + + * **HTTP, HTTPS, TLS**: Port, Hostname + * **TCP, UDP**: Port + + One **very** important rule to call out involves what happens when an + implementation: + + * Supports TCP protocol Listeners, as well as HTTP, HTTPS, or TLS protocol + Listeners, and + * sees HTTP, HTTPS, or TLS protocols with the same `port` as one with TCP + Protocol. + + In this case all the Listeners that share a port with the + TCP Listener are not distinct and so MUST NOT be accepted. + + If an implementation does not support TCP Protocol Listeners, then the + previous rule does not apply, and the TCP Listeners SHOULD NOT be + accepted. + + Note that the `tls` field is not used for determining if a listener is distinct, because + Listeners that _only_ differ on TLS config will still conflict in all cases. + + ### Listeners that are distinct only by Hostname + + When the Listeners are distinct based only on Hostname, inbound request + hostnames MUST match from the most specific to least specific Hostname + values to choose the correct Listener and its associated set of Routes. + + Exact matches MUST be processed before wildcard matches, and wildcard + matches MUST be processed before fallback (empty Hostname value) + matches. For example, `"foo.example.com"` takes precedence over + `"*.example.com"`, and `"*.example.com"` takes precedence over `""`. + + Additionally, if there are multiple wildcard entries, more specific + wildcard entries must be processed before less specific wildcard entries. + For example, `"*.foo.example.com"` takes precedence over `"*.example.com"`. + + The precise definition here is that the higher the number of dots in the + hostname to the right of the wildcard character, the higher the precedence. + + The wildcard character will match any number of characters _and dots_ to + the left, however, so `"*.example.com"` will match both + `"foo.bar.example.com"` _and_ `"bar.example.com"`. + + ## Handling indistinct Listeners + + If a set of Listeners contains Listeners that are not distinct, then those + Listeners are _Conflicted_, and the implementation MUST set the "Conflicted" + condition in the Listener Status to "True". + + The words "indistinct" and "conflicted" are considered equivalent for the + purpose of this documentation. + + Implementations MAY choose to accept a Gateway with some Conflicted + Listeners only if they only accept the partial Listener set that contains + no Conflicted Listeners. + + Specifically, an implementation MAY accept a partial Listener set subject to + the following rules: + + * The implementation MUST NOT pick one conflicting Listener as the winner. + ALL indistinct Listeners must not be accepted for processing. + * At least one distinct Listener MUST be present, or else the Gateway effectively + contains _no_ Listeners, and must be rejected from processing as a whole. + + The implementation MUST set a "ListenersNotValid" condition on the + Gateway Status when the Gateway contains Conflicted Listeners whether or + not they accept the Gateway. That Condition SHOULD clearly + indicate in the Message which Listeners are conflicted, and which are + Accepted. Additionally, the Listener status for those listeners SHOULD + indicate which Listeners are conflicted and not Accepted. + + ## General Listener behavior + + Note that, for all distinct Listeners, requests SHOULD match at most one Listener. + For example, if Listeners are defined for "foo.example.com" and "*.example.com", a + request to "foo.example.com" SHOULD only be routed using routes attached + to the "foo.example.com" Listener (and not the "*.example.com" Listener). + + This concept is known as "Listener Isolation", and it is an Extended feature + of Gateway API. Implementations that do not support Listener Isolation MUST + clearly document this, and MUST NOT claim support for the + `GatewayHTTPListenerIsolation` feature. + + Implementations that _do_ support Listener Isolation SHOULD claim support + for the Extended `GatewayHTTPListenerIsolation` feature and pass the associated + conformance tests. + + ## Compatible Listeners + + A Gateway's Listeners are considered _compatible_ if: + + 1. They are distinct. + 2. The implementation can serve them in compliance with the Addresses + requirement that all Listeners are available on all assigned + addresses. + + Compatible combinations in Extended support are expected to vary across + implementations. A combination that is compatible for one implementation + may not be compatible for another. + + For example, an implementation that cannot serve both TCP and UDP listeners + on the same address, or cannot mix HTTPS and generic TLS listens on the same port + would not consider those cases compatible, even though they are distinct. + + Implementations MAY merge separate Gateways onto a single set of + Addresses if all Listeners across all Gateways are compatible. + + In a future release the MinItems=1 requirement MAY be dropped. + + Support: Core + items: + description: |- + Listener embodies the concept of a logical endpoint where a Gateway accepts + network connections. + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: |- + AllowedRoutes defines the types of routes that MAY be attached to a + Listener and the trusted namespaces where those Route resources MAY be + present. + + Although a client request may match multiple route rules, only one rule + may ultimately receive the request. Matching precedence MUST be + determined in order of the following criteria: + + * The most specific match as defined by the Route type. + * The oldest Route based on creation timestamp. For example, a Route with + a creation timestamp of "2020-09-08 01:02:03" is given precedence over + a Route with a creation timestamp of "2020-09-08 01:02:04". + * If everything else is equivalent, the Route appearing first in + alphabetical order (namespace/name) should be given precedence. For + example, foo/bar is given precedence over foo/baz. + + All valid rules within a Route attached to this Listener should be + implemented. Invalid Route rules can be ignored (sometimes that will mean + the full Route). If a Route rule transitions from valid to invalid, + support for that Route rule should be dropped to ensure consistency. For + example, even if a filter specified by a Route rule is invalid, the rest + of the rules within that Route should still be supported. + + Support: Core + properties: + kinds: + description: |- + Kinds specifies the groups and kinds of Routes that are allowed to bind + to this Gateway Listener. When unspecified or empty, the kinds of Routes + selected are determined using the Listener protocol. + + A RouteGroupKind MUST correspond to kinds of Routes that are compatible + with the application protocol specified in the Listener's Protocol field. + If an implementation does not support or recognize this resource type, it + MUST set the "ResolvedRefs" condition to False for this Listener with the + "InvalidRouteKinds" reason. + + Support: Core + items: + description: RouteGroupKind indicates the group and kind + of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: |- + Namespaces indicates namespaces from which Routes may be attached to this + Listener. This is restricted to the namespace of this Gateway by default. + + Support: Core + properties: + from: + default: Same + description: |- + From indicates where Routes will be selected for this Gateway. Possible + values are: + + * All: Routes in all namespaces may be used by this Gateway. + * Selector: Routes in namespaces selected by the selector may be used by + this Gateway. + * Same: Only Routes in the same namespace may be used by this Gateway. + + Support: Core + enum: + - All + - Selector + - Same + type: string + selector: + description: |- + Selector must be specified when From is set to "Selector". In that case, + only Routes in Namespaces matching this Selector will be selected by this + Gateway. This field is ignored for other values of "From". + + Support: Core + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + type: object + type: object + hostname: + description: |- + Hostname specifies the virtual hostname to match for protocol types that + define this concept. When unspecified, all hostnames are matched. This + field is ignored for protocols that don't require hostname based + matching. + + Implementations MUST apply Hostname matching appropriately for each of + the following protocols: + + * TLS: The Listener Hostname MUST match the SNI. + * HTTP: The Listener Hostname MUST match the Host header of the request. + * HTTPS: The Listener Hostname SHOULD match both the SNI and Host header. + Note that this does not require the SNI and Host header to be the same. + The semantics of this are described in more detail below. + + To ensure security, Section 11.1 of RFC-6066 emphasizes that server + implementations that rely on SNI hostname matching MUST also verify + hostnames within the application protocol. + + Section 9.1.2 of RFC-7540 provides a mechanism for servers to reject the + reuse of a connection by responding with the HTTP 421 Misdirected Request + status code. This indicates that the origin server has rejected the + request because it appears to have been misdirected. + + To detect misdirected requests, Gateways SHOULD match the authority of + the requests with all the SNI hostname(s) configured across all the + Gateway Listeners on the same port and protocol: + + * If another Listener has an exact match or more specific wildcard entry, + the Gateway SHOULD return a 421. + * If the current Listener (selected by SNI matching during ClientHello) + does not match the Host: + * If another Listener does match the Host the Gateway SHOULD return a + 421. + * If no other Listener matches the Host, the Gateway MUST return a + 404. + + For HTTPRoute and TLSRoute resources, there is an interaction with the + `spec.hostnames` array. When both listener and route specify hostnames, + there MUST be an intersection between the values for a Route to be + accepted. For more information, refer to the Route specific Hostnames + documentation. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + name: + description: |- + Name is the name of the Listener. This name MUST be unique within a + Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: |- + Port is the network port. Multiple listeners may use the + same port, subject to the Listener compatibility rules. + + Support: Core + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: |- + Protocol specifies the network protocol this listener expects to receive. + + Support: Core + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: |- + TLS is the TLS configuration for the Listener. This field is required if + the Protocol field is "HTTPS" or "TLS". It is invalid to set this field + if the Protocol field is "HTTP", "TCP", or "UDP". + + The association of SNIs to Certificate defined in GatewayTLSConfig is + defined based on the Hostname field for this listener. + + The GatewayClass MUST use the longest matching SNI out of all + available certificates for any TLS handshake. + + Support: Core + properties: + certificateRefs: + description: |- + CertificateRefs contains a series of references to Kubernetes objects that + contains TLS certificates and private keys. These certificates are used to + establish a TLS handshake for requests that match the hostname of the + associated listener. + + A single CertificateRef to a Kubernetes Secret has "Core" support. + Implementations MAY choose to support attaching multiple certificates to + a Listener, but this behavior is implementation-specific. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + + This field is required to have at least one element when the mode is set + to "Terminate" (default) and is optional otherwise. + + CertificateRefs can reference to standard Kubernetes resources, i.e. + Secret, or implementation-specific custom resources. + + Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls + + Support: Implementation-specific (More than one reference or other resource types) + items: + description: |- + SecretObjectReference identifies an API object including its namespace, + defaulting to Secret. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example + "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + frontendValidation: + description: |- + FrontendValidation holds configuration information for validating the frontend (client). + Setting this field will require clients to send a client certificate + required for validation during the TLS handshake. In browsers this may result in a dialog appearing + that requests a user to specify the client certificate. + The maximum depth of a certificate chain accepted in verification is Implementation specific. + + Support: Extended + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to + Kubernetes objects that contain TLS certificates of + the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the client. + + A single CA certificate reference to a Kubernetes ConfigMap + has "Core" support. + Implementations MAY choose to support attaching multiple CA certificates to + a Listener, but this behavior is implementation-specific. + + Support: Core - A single reference to a Kubernetes ConfigMap + with the CA certificate in a key named `ca.crt`. + + Support: Implementation-specific (More than one reference, or other kinds + of resources). + + References to a resource in a different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + items: + description: |- + ObjectReference identifies an API object including its namespace. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When set to the empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "ConfigMap" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + maxItems: 8 + minItems: 1 + type: array + type: object + mode: + default: Terminate + description: |- + Mode defines the TLS behavior for the TLS session initiated by the client. + There are two possible modes: + + - Terminate: The TLS session between the downstream client and the + Gateway is terminated at the Gateway. This mode requires certificates + to be specified in some way, such as populating the certificateRefs + field. + - Passthrough: The TLS session is NOT terminated by the Gateway. This + implies that the Gateway can't decipher the TLS stream except for + the ClientHello message of the TLS protocol. The certificateRefs field + is ignored in this mode. + + Support: Core + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Options are a list of key/value pairs to enable extended TLS + configuration for each implementation. For example, configuring the + minimum TLS version or supported cipher suites. + + A set of common keys MAY be defined by the API in the future. To avoid + any ambiguity, implementation-specific definitions MUST use + domain-prefixed names, such as `example.com/my-custom-option`. + Un-prefixed names are reserved for key names defined by Gateway API. + + Support: Implementation-specific + maxProperties: 16 + type: object + type: object + x-kubernetes-validations: + - message: certificateRefs or options must be specified when + mode is Terminate + rule: 'self.mode == ''Terminate'' ? size(self.certificateRefs) + > 0 || size(self.options) > 0 : true' + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: tls must not be specified for protocols ['HTTP', 'TCP', + 'UDP'] + rule: 'self.all(l, l.protocol in [''HTTP'', ''TCP'', ''UDP''] ? + !has(l.tls) : true)' + - message: tls mode must be Terminate for protocol HTTPS + rule: 'self.all(l, (l.protocol == ''HTTPS'' && has(l.tls)) ? (l.tls.mode + == '''' || l.tls.mode == ''Terminate'') : true)' + - message: hostname must not be specified for protocols ['TCP', 'UDP'] + rule: 'self.all(l, l.protocol in [''TCP'', ''UDP''] ? (!has(l.hostname) + || l.hostname == '''') : true)' + - message: Listener name must be unique within the Gateway + rule: self.all(l1, self.exists_one(l2, l1.name == l2.name)) + - message: Combination of port, protocol and hostname must be unique + for each listener + rule: 'self.all(l1, self.exists_one(l2, l1.port == l2.port && l1.protocol + == l2.protocol && (has(l1.hostname) && has(l2.hostname) ? l1.hostname + == l2.hostname : !has(l1.hostname) && !has(l2.hostname))))' + required: + - gatewayClassName + - listeners + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: Status defines the current state of Gateway. + properties: + addresses: + description: |- + Addresses lists the network addresses that have been bound to the + Gateway. + + This list may differ from the addresses provided in the spec under some + conditions: + + * no addresses are specified, all addresses are dynamically assigned + * a combination of specified and dynamic addresses are assigned + * a specified address was unusable (e.g. already in use) + items: + description: GatewayStatusAddress describes a network address that + is bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: |- + Value of the address. The validity of the values will depend + on the type and support by the controller. + + Examples: `1.2.3.4`, `128::1`, `my-ip-address`. + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching + ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + true' + maxItems: 16 + type: array + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: |- + Conditions describe the current conditions of the Gateway. + + Implementations should prefer to express Gateway conditions + using the `GatewayConditionType` and `GatewayConditionReason` + constants so that operators and tools can converge on a common + vocabulary to describe Gateway state. + + Known condition types are: + + * "Accepted" + * "Programmed" + * "Ready" + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port + defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: |- + AttachedRoutes represents the total number of Routes that have been + successfully attached to this Listener. + + Successful attachment of a Route to a Listener is based solely on the + combination of the AllowedRoutes field on the corresponding Listener + and the Route's ParentRefs field. A Route is successfully attached to + a Listener when it is selected by the Listener's AllowedRoutes field + AND the Route has a valid ParentRef selecting the whole Gateway + resource or a specific Listener as a parent resource (more detail on + attachment semantics can be found in the documentation on the various + Route kinds ParentRefs fields). Listener or Route status does not impact + successful attachment, i.e. the AttachedRoutes field count MUST be set + for Listeners with condition Accepted: false and MUST count successfully + attached Routes that may themselves have Accepted: false conditions. + + Uses for this field include troubleshooting Route attachment and + measuring blast radius/impact of changes to a Listener. + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this + listener. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status + corresponds to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + supportedKinds: + description: |- + SupportedKinds is the list indicating the Kinds supported by this + listener. This MUST represent the kinds an implementation supports for + that Listener configuration. + + If kinds are specified in Spec that are not supported, they MUST NOT + appear in this list and an implementation MUST set the "ResolvedRefs" + condition to "False" with the "InvalidRouteKinds" reason. If both valid + and invalid Route kinds are specified, the implementation MUST + reference the valid Route kinds that have been specified. + items: + description: RouteGroupKind indicates the group and kind of + a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.gatewayClassName + name: Class + type: string + - jsonPath: .status.addresses[*].value + name: Address + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: |- + Gateway represents an instance of a service-traffic handling infrastructure + by binding Listeners to a set of IP addresses. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of Gateway. + properties: + addresses: + description: |- + Addresses requested for this Gateway. This is optional and behavior can + depend on the implementation. If a value is set in the spec and the + requested address is invalid or unavailable, the implementation MUST + indicate this in the associated entry in GatewayStatus.Addresses. + + The Addresses field represents a request for the address(es) on the + "outside of the Gateway", that traffic bound for this Gateway will use. + This could be the IP address or hostname of an external load balancer or + other networking infrastructure, or some other address that traffic will + be sent to. + + If no Addresses are specified, the implementation MAY schedule the + Gateway in an implementation-specific manner, assigning an appropriate + set of Addresses. + + The implementation MUST bind all Listeners to every GatewayAddress that + it assigns to the Gateway and add a corresponding entry in + GatewayStatus.Addresses. + + Support: Extended + items: + description: GatewaySpecAddress describes an address that can be + bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: |- + When a value is unspecified, an implementation SHOULD automatically + assign an address matching the requested type if possible. + + If an implementation does not support an empty value, they MUST set the + "Programmed" condition in status to False with a reason of "AddressNotAssigned". + + Examples: `1.2.3.4`, `128::1`, `my-ip-address`. + maxLength: 253 + type: string + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching + ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + true' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: IPAddress values must be unique + rule: 'self.all(a1, a1.type == ''IPAddress'' ? self.exists_one(a2, + a2.type == a1.type && a2.value == a1.value) : true )' + - message: Hostname values must be unique + rule: 'self.all(a1, a1.type == ''Hostname'' ? self.exists_one(a2, + a2.type == a1.type && a2.value == a1.value) : true )' + allowedListeners: + description: |- + AllowedListeners defines which ListenerSets can be attached to this Gateway. + While this feature is experimental, the default value is to allow no ListenerSets. + properties: + namespaces: + default: + from: None + description: |- + Namespaces defines which namespaces ListenerSets can be attached to this Gateway. + While this feature is experimental, the default value is to allow no ListenerSets. + properties: + from: + default: None + description: |- + From indicates where ListenerSets can attach to this Gateway. Possible + values are: + + * Same: Only ListenerSets in the same namespace may be attached to this Gateway. + * Selector: ListenerSets in namespaces selected by the selector may be attached to this Gateway. + * All: ListenerSets in all namespaces may be attached to this Gateway. + * None: Only listeners defined in the Gateway's spec are allowed + + While this feature is experimental, the default value None + enum: + - All + - Selector + - Same + - None + type: string + selector: + description: |- + Selector must be specified when From is set to "Selector". In that case, + only ListenerSets in Namespaces matching this Selector will be selected by this + Gateway. This field is ignored for other values of "From". + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + type: object + type: object + backendTLS: + description: |- + BackendTLS configures TLS settings for when this Gateway is connecting to + backends with TLS. + + Support: Core + properties: + clientCertificateRef: + description: |- + ClientCertificateRef is a reference to an object that contains a Client + Certificate and the associated private key. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + + ClientCertificateRef can reference to standard Kubernetes resources, i.e. + Secret, or implementation-specific custom resources. + + This setting can be overridden on the service level by use of BackendTLSPolicy. + + Support: Core + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + type: object + gatewayClassName: + description: |- + GatewayClassName used for this Gateway. This is the name of a + GatewayClass resource. + maxLength: 253 + minLength: 1 + type: string + infrastructure: + description: |- + Infrastructure defines infrastructure level attributes about this Gateway instance. + + Support: Extended + properties: + annotations: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Annotations that SHOULD be applied to any resources created in response to this Gateway. + + For implementations creating other Kubernetes objects, this should be the `metadata.annotations` field on resources. + For other implementations, this refers to any relevant (implementation specific) "annotations" concepts. + + An implementation may chose to add additional implementation-specific annotations as they see fit. + + Support: Extended + maxProperties: 8 + type: object + x-kubernetes-validations: + - message: Annotation keys must be in the form of an optional + DNS subdomain prefix followed by a required name segment of + up to 63 characters. + rule: self.all(key, key.matches(r"""^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9]$""")) + - message: If specified, the annotation key's prefix must be a + DNS subdomain not longer than 253 characters in total. + rule: self.all(key, key.split("/")[0].size() < 253) + labels: + additionalProperties: + description: |- + LabelValue is the value of a label in the Gateway API. This is used for validation + of maps such as Gateway infrastructure labels. This matches the Kubernetes + label validation rules: + * must be 63 characters or less (can be empty), + * unless empty, must begin and end with an alphanumeric character ([a-z0-9A-Z]), + * could contain dashes (-), underscores (_), dots (.), and alphanumerics between. + + Valid values include: + + * MyValue + * my.name + * 123-my-value + maxLength: 63 + minLength: 0 + pattern: ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$ + type: string + description: |- + Labels that SHOULD be applied to any resources created in response to this Gateway. + + For implementations creating other Kubernetes objects, this should be the `metadata.labels` field on resources. + For other implementations, this refers to any relevant (implementation specific) "labels" concepts. + + An implementation may chose to add additional implementation-specific labels as they see fit. + + If an implementation maps these labels to Pods, or any other resource that would need to be recreated when labels + change, it SHOULD clearly warn about this behavior in documentation. + + Support: Extended + maxProperties: 8 + type: object + x-kubernetes-validations: + - message: Label keys must be in the form of an optional DNS subdomain + prefix followed by a required name segment of up to 63 characters. + rule: self.all(key, key.matches(r"""^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9]$""")) + - message: If specified, the label key's prefix must be a DNS + subdomain not longer than 253 characters in total. + rule: self.all(key, key.split("/")[0].size() < 253) + parametersRef: + description: |- + ParametersRef is a reference to a resource that contains the configuration + parameters corresponding to the Gateway. This is optional if the + controller does not require any additional configuration. + + This follows the same semantics as GatewayClass's `parametersRef`, but on a per-Gateway basis + + The Gateway's GatewayClass may provide its own `parametersRef`. When both are specified, + the merging behavior is implementation specific. + It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway. + + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the Gateway SHOULD be + rejected with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + + Support: Implementation-specific + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + type: object + listeners: + description: |- + Listeners associated with this Gateway. Listeners define + logical endpoints that are bound on this Gateway's addresses. + At least one Listener MUST be specified. + + ## Distinct Listeners + + Each Listener in a set of Listeners (for example, in a single Gateway) + MUST be _distinct_, in that a traffic flow MUST be able to be assigned to + exactly one listener. (This section uses "set of Listeners" rather than + "Listeners in a single Gateway" because implementations MAY merge configuration + from multiple Gateways onto a single data plane, and these rules _also_ + apply in that case). + + Practically, this means that each listener in a set MUST have a unique + combination of Port, Protocol, and, if supported by the protocol, Hostname. + + Some combinations of port, protocol, and TLS settings are considered + Core support and MUST be supported by implementations based on the objects + they support: + + HTTPRoute + + 1. HTTPRoute, Port: 80, Protocol: HTTP + 2. HTTPRoute, Port: 443, Protocol: HTTPS, TLS Mode: Terminate, TLS keypair provided + + TLSRoute + + 1. TLSRoute, Port: 443, Protocol: TLS, TLS Mode: Passthrough + + "Distinct" Listeners have the following property: + + **The implementation can match inbound requests to a single distinct + Listener**. + + When multiple Listeners share values for fields (for + example, two Listeners with the same Port value), the implementation + can match requests to only one of the Listeners using other + Listener fields. + + When multiple listeners have the same value for the Protocol field, then + each of the Listeners with matching Protocol values MUST have different + values for other fields. + + The set of fields that MUST be different for a Listener differs per protocol. + The following rules define the rules for what fields MUST be considered for + Listeners to be distinct with each protocol currently defined in the + Gateway API spec. + + The set of listeners that all share a protocol value MUST have _different_ + values for _at least one_ of these fields to be distinct: + + * **HTTP, HTTPS, TLS**: Port, Hostname + * **TCP, UDP**: Port + + One **very** important rule to call out involves what happens when an + implementation: + + * Supports TCP protocol Listeners, as well as HTTP, HTTPS, or TLS protocol + Listeners, and + * sees HTTP, HTTPS, or TLS protocols with the same `port` as one with TCP + Protocol. + + In this case all the Listeners that share a port with the + TCP Listener are not distinct and so MUST NOT be accepted. + + If an implementation does not support TCP Protocol Listeners, then the + previous rule does not apply, and the TCP Listeners SHOULD NOT be + accepted. + + Note that the `tls` field is not used for determining if a listener is distinct, because + Listeners that _only_ differ on TLS config will still conflict in all cases. + + ### Listeners that are distinct only by Hostname + + When the Listeners are distinct based only on Hostname, inbound request + hostnames MUST match from the most specific to least specific Hostname + values to choose the correct Listener and its associated set of Routes. + + Exact matches MUST be processed before wildcard matches, and wildcard + matches MUST be processed before fallback (empty Hostname value) + matches. For example, `"foo.example.com"` takes precedence over + `"*.example.com"`, and `"*.example.com"` takes precedence over `""`. + + Additionally, if there are multiple wildcard entries, more specific + wildcard entries must be processed before less specific wildcard entries. + For example, `"*.foo.example.com"` takes precedence over `"*.example.com"`. + + The precise definition here is that the higher the number of dots in the + hostname to the right of the wildcard character, the higher the precedence. + + The wildcard character will match any number of characters _and dots_ to + the left, however, so `"*.example.com"` will match both + `"foo.bar.example.com"` _and_ `"bar.example.com"`. + + ## Handling indistinct Listeners + + If a set of Listeners contains Listeners that are not distinct, then those + Listeners are _Conflicted_, and the implementation MUST set the "Conflicted" + condition in the Listener Status to "True". + + The words "indistinct" and "conflicted" are considered equivalent for the + purpose of this documentation. + + Implementations MAY choose to accept a Gateway with some Conflicted + Listeners only if they only accept the partial Listener set that contains + no Conflicted Listeners. + + Specifically, an implementation MAY accept a partial Listener set subject to + the following rules: + + * The implementation MUST NOT pick one conflicting Listener as the winner. + ALL indistinct Listeners must not be accepted for processing. + * At least one distinct Listener MUST be present, or else the Gateway effectively + contains _no_ Listeners, and must be rejected from processing as a whole. + + The implementation MUST set a "ListenersNotValid" condition on the + Gateway Status when the Gateway contains Conflicted Listeners whether or + not they accept the Gateway. That Condition SHOULD clearly + indicate in the Message which Listeners are conflicted, and which are + Accepted. Additionally, the Listener status for those listeners SHOULD + indicate which Listeners are conflicted and not Accepted. + + ## General Listener behavior + + Note that, for all distinct Listeners, requests SHOULD match at most one Listener. + For example, if Listeners are defined for "foo.example.com" and "*.example.com", a + request to "foo.example.com" SHOULD only be routed using routes attached + to the "foo.example.com" Listener (and not the "*.example.com" Listener). + + This concept is known as "Listener Isolation", and it is an Extended feature + of Gateway API. Implementations that do not support Listener Isolation MUST + clearly document this, and MUST NOT claim support for the + `GatewayHTTPListenerIsolation` feature. + + Implementations that _do_ support Listener Isolation SHOULD claim support + for the Extended `GatewayHTTPListenerIsolation` feature and pass the associated + conformance tests. + + ## Compatible Listeners + + A Gateway's Listeners are considered _compatible_ if: + + 1. They are distinct. + 2. The implementation can serve them in compliance with the Addresses + requirement that all Listeners are available on all assigned + addresses. + + Compatible combinations in Extended support are expected to vary across + implementations. A combination that is compatible for one implementation + may not be compatible for another. + + For example, an implementation that cannot serve both TCP and UDP listeners + on the same address, or cannot mix HTTPS and generic TLS listens on the same port + would not consider those cases compatible, even though they are distinct. + + Implementations MAY merge separate Gateways onto a single set of + Addresses if all Listeners across all Gateways are compatible. + + In a future release the MinItems=1 requirement MAY be dropped. + + Support: Core + items: + description: |- + Listener embodies the concept of a logical endpoint where a Gateway accepts + network connections. + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: |- + AllowedRoutes defines the types of routes that MAY be attached to a + Listener and the trusted namespaces where those Route resources MAY be + present. + + Although a client request may match multiple route rules, only one rule + may ultimately receive the request. Matching precedence MUST be + determined in order of the following criteria: + + * The most specific match as defined by the Route type. + * The oldest Route based on creation timestamp. For example, a Route with + a creation timestamp of "2020-09-08 01:02:03" is given precedence over + a Route with a creation timestamp of "2020-09-08 01:02:04". + * If everything else is equivalent, the Route appearing first in + alphabetical order (namespace/name) should be given precedence. For + example, foo/bar is given precedence over foo/baz. + + All valid rules within a Route attached to this Listener should be + implemented. Invalid Route rules can be ignored (sometimes that will mean + the full Route). If a Route rule transitions from valid to invalid, + support for that Route rule should be dropped to ensure consistency. For + example, even if a filter specified by a Route rule is invalid, the rest + of the rules within that Route should still be supported. + + Support: Core + properties: + kinds: + description: |- + Kinds specifies the groups and kinds of Routes that are allowed to bind + to this Gateway Listener. When unspecified or empty, the kinds of Routes + selected are determined using the Listener protocol. + + A RouteGroupKind MUST correspond to kinds of Routes that are compatible + with the application protocol specified in the Listener's Protocol field. + If an implementation does not support or recognize this resource type, it + MUST set the "ResolvedRefs" condition to False for this Listener with the + "InvalidRouteKinds" reason. + + Support: Core + items: + description: RouteGroupKind indicates the group and kind + of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: |- + Namespaces indicates namespaces from which Routes may be attached to this + Listener. This is restricted to the namespace of this Gateway by default. + + Support: Core + properties: + from: + default: Same + description: |- + From indicates where Routes will be selected for this Gateway. Possible + values are: + + * All: Routes in all namespaces may be used by this Gateway. + * Selector: Routes in namespaces selected by the selector may be used by + this Gateway. + * Same: Only Routes in the same namespace may be used by this Gateway. + + Support: Core + enum: + - All + - Selector + - Same + type: string + selector: + description: |- + Selector must be specified when From is set to "Selector". In that case, + only Routes in Namespaces matching this Selector will be selected by this + Gateway. This field is ignored for other values of "From". + + Support: Core + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + type: object + type: object + hostname: + description: |- + Hostname specifies the virtual hostname to match for protocol types that + define this concept. When unspecified, all hostnames are matched. This + field is ignored for protocols that don't require hostname based + matching. + + Implementations MUST apply Hostname matching appropriately for each of + the following protocols: + + * TLS: The Listener Hostname MUST match the SNI. + * HTTP: The Listener Hostname MUST match the Host header of the request. + * HTTPS: The Listener Hostname SHOULD match both the SNI and Host header. + Note that this does not require the SNI and Host header to be the same. + The semantics of this are described in more detail below. + + To ensure security, Section 11.1 of RFC-6066 emphasizes that server + implementations that rely on SNI hostname matching MUST also verify + hostnames within the application protocol. + + Section 9.1.2 of RFC-7540 provides a mechanism for servers to reject the + reuse of a connection by responding with the HTTP 421 Misdirected Request + status code. This indicates that the origin server has rejected the + request because it appears to have been misdirected. + + To detect misdirected requests, Gateways SHOULD match the authority of + the requests with all the SNI hostname(s) configured across all the + Gateway Listeners on the same port and protocol: + + * If another Listener has an exact match or more specific wildcard entry, + the Gateway SHOULD return a 421. + * If the current Listener (selected by SNI matching during ClientHello) + does not match the Host: + * If another Listener does match the Host the Gateway SHOULD return a + 421. + * If no other Listener matches the Host, the Gateway MUST return a + 404. + + For HTTPRoute and TLSRoute resources, there is an interaction with the + `spec.hostnames` array. When both listener and route specify hostnames, + there MUST be an intersection between the values for a Route to be + accepted. For more information, refer to the Route specific Hostnames + documentation. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + name: + description: |- + Name is the name of the Listener. This name MUST be unique within a + Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: |- + Port is the network port. Multiple listeners may use the + same port, subject to the Listener compatibility rules. + + Support: Core + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: |- + Protocol specifies the network protocol this listener expects to receive. + + Support: Core + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: |- + TLS is the TLS configuration for the Listener. This field is required if + the Protocol field is "HTTPS" or "TLS". It is invalid to set this field + if the Protocol field is "HTTP", "TCP", or "UDP". + + The association of SNIs to Certificate defined in GatewayTLSConfig is + defined based on the Hostname field for this listener. + + The GatewayClass MUST use the longest matching SNI out of all + available certificates for any TLS handshake. + + Support: Core + properties: + certificateRefs: + description: |- + CertificateRefs contains a series of references to Kubernetes objects that + contains TLS certificates and private keys. These certificates are used to + establish a TLS handshake for requests that match the hostname of the + associated listener. + + A single CertificateRef to a Kubernetes Secret has "Core" support. + Implementations MAY choose to support attaching multiple certificates to + a Listener, but this behavior is implementation-specific. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + + This field is required to have at least one element when the mode is set + to "Terminate" (default) and is optional otherwise. + + CertificateRefs can reference to standard Kubernetes resources, i.e. + Secret, or implementation-specific custom resources. + + Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls + + Support: Implementation-specific (More than one reference or other resource types) + items: + description: |- + SecretObjectReference identifies an API object including its namespace, + defaulting to Secret. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example + "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + frontendValidation: + description: |- + FrontendValidation holds configuration information for validating the frontend (client). + Setting this field will require clients to send a client certificate + required for validation during the TLS handshake. In browsers this may result in a dialog appearing + that requests a user to specify the client certificate. + The maximum depth of a certificate chain accepted in verification is Implementation specific. + + Support: Extended + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to + Kubernetes objects that contain TLS certificates of + the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the client. + + A single CA certificate reference to a Kubernetes ConfigMap + has "Core" support. + Implementations MAY choose to support attaching multiple CA certificates to + a Listener, but this behavior is implementation-specific. + + Support: Core - A single reference to a Kubernetes ConfigMap + with the CA certificate in a key named `ca.crt`. + + Support: Implementation-specific (More than one reference, or other kinds + of resources). + + References to a resource in a different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + items: + description: |- + ObjectReference identifies an API object including its namespace. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When set to the empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "ConfigMap" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + maxItems: 8 + minItems: 1 + type: array + type: object + mode: + default: Terminate + description: |- + Mode defines the TLS behavior for the TLS session initiated by the client. + There are two possible modes: + + - Terminate: The TLS session between the downstream client and the + Gateway is terminated at the Gateway. This mode requires certificates + to be specified in some way, such as populating the certificateRefs + field. + - Passthrough: The TLS session is NOT terminated by the Gateway. This + implies that the Gateway can't decipher the TLS stream except for + the ClientHello message of the TLS protocol. The certificateRefs field + is ignored in this mode. + + Support: Core + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Options are a list of key/value pairs to enable extended TLS + configuration for each implementation. For example, configuring the + minimum TLS version or supported cipher suites. + + A set of common keys MAY be defined by the API in the future. To avoid + any ambiguity, implementation-specific definitions MUST use + domain-prefixed names, such as `example.com/my-custom-option`. + Un-prefixed names are reserved for key names defined by Gateway API. + + Support: Implementation-specific + maxProperties: 16 + type: object + type: object + x-kubernetes-validations: + - message: certificateRefs or options must be specified when + mode is Terminate + rule: 'self.mode == ''Terminate'' ? size(self.certificateRefs) + > 0 || size(self.options) > 0 : true' + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: tls must not be specified for protocols ['HTTP', 'TCP', + 'UDP'] + rule: 'self.all(l, l.protocol in [''HTTP'', ''TCP'', ''UDP''] ? + !has(l.tls) : true)' + - message: tls mode must be Terminate for protocol HTTPS + rule: 'self.all(l, (l.protocol == ''HTTPS'' && has(l.tls)) ? (l.tls.mode + == '''' || l.tls.mode == ''Terminate'') : true)' + - message: hostname must not be specified for protocols ['TCP', 'UDP'] + rule: 'self.all(l, l.protocol in [''TCP'', ''UDP''] ? (!has(l.hostname) + || l.hostname == '''') : true)' + - message: Listener name must be unique within the Gateway + rule: self.all(l1, self.exists_one(l2, l1.name == l2.name)) + - message: Combination of port, protocol and hostname must be unique + for each listener + rule: 'self.all(l1, self.exists_one(l2, l1.port == l2.port && l1.protocol + == l2.protocol && (has(l1.hostname) && has(l2.hostname) ? l1.hostname + == l2.hostname : !has(l1.hostname) && !has(l2.hostname))))' + required: + - gatewayClassName + - listeners + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: Status defines the current state of Gateway. + properties: + addresses: + description: |- + Addresses lists the network addresses that have been bound to the + Gateway. + + This list may differ from the addresses provided in the spec under some + conditions: + + * no addresses are specified, all addresses are dynamically assigned + * a combination of specified and dynamic addresses are assigned + * a specified address was unusable (e.g. already in use) + items: + description: GatewayStatusAddress describes a network address that + is bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: |- + Value of the address. The validity of the values will depend + on the type and support by the controller. + + Examples: `1.2.3.4`, `128::1`, `my-ip-address`. + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching + ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + true' + maxItems: 16 + type: array + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: |- + Conditions describe the current conditions of the Gateway. + + Implementations should prefer to express Gateway conditions + using the `GatewayConditionType` and `GatewayConditionReason` + constants so that operators and tools can converge on a common + vocabulary to describe Gateway state. + + Known condition types are: + + * "Accepted" + * "Programmed" + * "Ready" + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port + defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: |- + AttachedRoutes represents the total number of Routes that have been + successfully attached to this Listener. + + Successful attachment of a Route to a Listener is based solely on the + combination of the AllowedRoutes field on the corresponding Listener + and the Route's ParentRefs field. A Route is successfully attached to + a Listener when it is selected by the Listener's AllowedRoutes field + AND the Route has a valid ParentRef selecting the whole Gateway + resource or a specific Listener as a parent resource (more detail on + attachment semantics can be found in the documentation on the various + Route kinds ParentRefs fields). Listener or Route status does not impact + successful attachment, i.e. the AttachedRoutes field count MUST be set + for Listeners with condition Accepted: false and MUST count successfully + attached Routes that may themselves have Accepted: false conditions. + + Uses for this field include troubleshooting Route attachment and + measuring blast radius/impact of changes to a Listener. + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this + listener. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status + corresponds to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + supportedKinds: + description: |- + SupportedKinds is the list indicating the Kinds supported by this + listener. This MUST represent the kinds an implementation supports for + that Listener configuration. + + If kinds are specified in Spec that are not supported, they MUST NOT + appear in this list and an implementation MUST set the "ResolvedRefs" + condition to "False" with the "InvalidRouteKinds" reason. If both valid + and invalid Route kinds are specified, the implementation MUST + reference the valid Route kinds that have been specified. + items: + description: RouteGroupKind indicates the group and kind of + a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_grpcroutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: grpcroutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: GRPCRoute + listKind: GRPCRouteList + plural: grpcroutes + singular: grpcroute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + GRPCRoute provides a way to route gRPC requests. This includes the capability + to match requests by hostname, gRPC service, gRPC method, or HTTP/2 header. + Filters can be used to specify additional processing steps. Backends specify + where matching requests will be routed. + + GRPCRoute falls under extended support within the Gateway API. Within the + following specification, the word "MUST" indicates that an implementation + supporting GRPCRoute must conform to the indicated requirement, but an + implementation not supporting this route type need not follow the requirement + unless explicitly indicated. + + Implementations supporting `GRPCRoute` with the `HTTPS` `ProtocolType` MUST + accept HTTP/2 connections without an initial upgrade from HTTP/1.1, i.e. via + ALPN. If the implementation does not support this, then it MUST set the + "Accepted" condition to "False" for the affected listener with a reason of + "UnsupportedProtocol". Implementations MAY also accept HTTP/2 connections + with an upgrade from HTTP/1. + + Implementations supporting `GRPCRoute` with the `HTTP` `ProtocolType` MUST + support HTTP/2 over cleartext TCP (h2c, + https://www.rfc-editor.org/rfc/rfc7540#section-3.1) without an initial + upgrade from HTTP/1.1, i.e. with prior knowledge + (https://www.rfc-editor.org/rfc/rfc7540#section-3.4). If the implementation + does not support this, then it MUST set the "Accepted" condition to "False" + for the affected listener with a reason of "UnsupportedProtocol". + Implementations MAY also accept HTTP/2 connections with an upgrade from + HTTP/1, i.e. without prior knowledge. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GRPCRoute. + properties: + hostnames: + description: |- + Hostnames defines a set of hostnames to match against the GRPC + Host header to select a GRPCRoute to process the request. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label MUST appear by itself as the first label. + + If a hostname is specified by both the Listener and GRPCRoute, there + MUST be at least one intersecting hostname for the GRPCRoute to be + attached to the Listener. For example: + + * A Listener with `test.example.com` as the hostname matches GRPCRoutes + that have either not specified any hostnames, or have specified at + least one of `test.example.com` or `*.example.com`. + * A Listener with `*.example.com` as the hostname matches GRPCRoutes + that have either not specified any hostnames or have specified at least + one hostname that matches the Listener hostname. For example, + `test.example.com` and `*.example.com` would both match. On the other + hand, `example.com` and `test.example.net` would not match. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + + If both the Listener and GRPCRoute have specified hostnames, any + GRPCRoute hostnames that do not match the Listener hostname MUST be + ignored. For example, if a Listener specified `*.example.com`, and the + GRPCRoute specified `test.example.com` and `test.example.net`, + `test.example.net` MUST NOT be considered for a match. + + If both the Listener and GRPCRoute have specified hostnames, and none + match with the criteria above, then the GRPCRoute MUST NOT be accepted by + the implementation. The implementation MUST raise an 'Accepted' Condition + with a status of `False` in the corresponding RouteParentStatus. + + If a Route (A) of type HTTPRoute or GRPCRoute is attached to a + Listener and that listener already has another Route (B) of the other + type attached and the intersection of the hostnames of A and B is + non-empty, then the implementation MUST accept exactly one of these two + routes, determined by the following criteria, in order: + + * The oldest Route based on creation timestamp. + * The Route appearing first in alphabetical order by + "{namespace}/{name}". + + The rejected Route MUST raise an 'Accepted' condition with a status of + 'False' in the corresponding RouteParentStatus. + + Support: Core + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) + rules: + description: Rules are a list of GRPC matchers, filters and actions. + items: + description: |- + GRPCRouteRule defines the semantics for matching a gRPC request based on + conditions (matches), processing it (filters), and forwarding the request to + an API object (backendRefs). + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. + + Failure behavior here depends on how many BackendRefs are specified and + how many are invalid. + + If *all* entries in BackendRefs are invalid, and there are also no filters + specified in this route rule, *all* traffic which matches this rule MUST + receive an `UNAVAILABLE` status. + + See the GRPCBackendRef definition for the rules about what makes a single + GRPCBackendRef invalid. + + When a GRPCBackendRef is invalid, `UNAVAILABLE` statuses MUST be returned for + requests that would have otherwise been routed to an invalid backend. If + multiple backends are specified, and some are invalid, the proportion of + requests that would otherwise have been routed to an invalid backend + MUST receive an `UNAVAILABLE` status. + + For example, if two backends are specified with equal weights, and one is + invalid, 50 percent of traffic MUST receive an `UNAVAILABLE` status. + Implementations may choose how that 50 percent is determined. + + Support: Core for Kubernetes Service + + Support: Implementation-specific for any other resource + + Support for weight: Core + items: + description: |- + GRPCBackendRef defines how a GRPCRoute forwards a gRPC request. + + Note that when a namespace different than the local namespace is specified, a + ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + + When the BackendRef points to a Kubernetes Service, implementations SHOULD + honor the appProtocol field if it is set for the target Service Port. + + Implementations supporting appProtocol SHOULD recognize the Kubernetes + Standard Application Protocols defined in KEP-3726. + + If a Service appProtocol isn't specified, an implementation MAY infer the + backend protocol through its own means. Implementations MAY infer the + protocol from the Route type referring to the backend Service. + + If a Route is not able to send traffic to the backend using the specified + protocol then the backend is considered invalid. Implementations MUST set the + "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. + properties: + filters: + description: |- + Filters defined at this level MUST be executed if and only if the + request is being forwarded to the backend defined here. + + Support: Implementation-specific (For broader support of filters, use the + Filters field in GRPCRouteRule.) + items: + description: |- + GRPCRouteFilter defines processing steps that must be completed during the + request or response lifecycle. GRPCRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + Support: Implementation-specific + + This filter can be used multiple times within the same rule. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal + to denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be + specified in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations supporting GRPCRoute MUST support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` MUST be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + enum: + - ResponseHeaderModifier + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil + if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type + != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type + == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil + if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type + != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for + RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == + ''RequestMirror'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for + ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + filters: + description: |- + Filters define the filters that are applied to requests that match + this rule. + + The effects of ordering of multiple behaviors are currently unspecified. + This can change in the future based on feedback during the alpha stage. + + Conformance-levels at this level are defined based on the type of filter: + + - ALL core filters MUST be supported by all implementations that support + GRPCRoute. + - Implementers are encouraged to support extended filters. + - Implementation-specific custom filters have no API guarantees across + implementations. + + Specifying the same filter multiple times is not supported unless explicitly + indicated in the filter. + + If an implementation cannot support a combination of filters, it must clearly + document that limitation. In cases where incompatible or unsupported + filters are specified and cause the `Accepted` condition to be set to status + `False`, implementations may use the `IncompatibleFilters` reason to specify + this configuration error. + + Support: Core + items: + description: |- + GRPCRouteFilter defines processing steps that must be completed during the + request or response lifecycle. GRPCRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + Support: Implementation-specific + + This filter can be used multiple times within the same rule. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal to + denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be specified + in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations supporting GRPCRoute MUST support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` MUST be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + enum: + - ResponseHeaderModifier + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the + filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != + ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == + ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the + filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != + ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror + filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef + filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + matches: + description: |- + Matches define conditions used for matching the rule against incoming + gRPC requests. Each match is independent, i.e. this rule will be matched + if **any** one of the matches is satisfied. + + For example, take the following matches configuration: + + ``` + matches: + - method: + service: foo.bar + headers: + values: + version: 2 + - method: + service: foo.bar.v2 + ``` + + For a request to match against this rule, it MUST satisfy + EITHER of the two conditions: + + - service of foo.bar AND contains the header `version: 2` + - service of foo.bar.v2 + + See the documentation for GRPCRouteMatch on how to specify multiple + match conditions to be ANDed together. + + If no matches are specified, the implementation MUST match every gRPC request. + + Proxy or Load Balancer routing configuration generated from GRPCRoutes + MUST prioritize rules based on the following criteria, continuing on + ties. Merging MUST not be done between GRPCRoutes and HTTPRoutes. + Precedence MUST be given to the rule with the largest number of: + + * Characters in a matching non-wildcard hostname. + * Characters in a matching hostname. + * Characters in a matching service. + * Characters in a matching method. + * Header matches. + + If ties still exist across multiple Routes, matching precedence MUST be + determined in order of the following criteria, continuing on ties: + + * The oldest Route based on creation timestamp. + * The Route appearing first in alphabetical order by + "{namespace}/{name}". + + If ties still exist within the Route that has been given precedence, + matching precedence MUST be granted to the first matching rule meeting + the above criteria. + items: + description: |- + GRPCRouteMatch defines the predicate used to match requests to a given + action. Multiple match types are ANDed together, i.e. the match will + evaluate to true only if all conditions are satisfied. + + For example, the match below will match a gRPC request only if its service + is `foo` AND it contains the `version: v1` header: + + ``` + matches: + - method: + type: Exact + service: "foo" + headers: + - name: "version" + value "v1" + + ``` + properties: + headers: + description: |- + Headers specifies gRPC request header matchers. Multiple match values are + ANDed together, meaning, a request MUST match all the specified headers + to select the route. + items: + description: |- + GRPCHeaderMatch describes how to select a gRPC route by matching gRPC request + headers. + properties: + name: + description: |- + Name is the name of the gRPC Header to be matched. + + If multiple entries specify equivalent header names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: Type specifies how to match against + the value of the header. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of the gRPC Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: |- + Method specifies a gRPC request service/method matcher. If this field is + not specified, all services and methods will match. + properties: + method: + description: |- + Value of the method to match against. If left empty or omitted, will + match all services. + + At least one of Service and Method MUST be a non-empty string. + maxLength: 1024 + type: string + service: + description: |- + Value of the service to match against. If left empty or omitted, will + match any service. + + At least one of Service and Method MUST be a non-empty string. + maxLength: 1024 + type: string + type: + default: Exact + description: |- + Type specifies how to match against the service and/or method. + Support: Core (Exact with service and method specified) + + Support: Implementation-specific (Exact with method specified but no service specified) + + Support: Implementation-specific (RegularExpression) + enum: + - Exact + - RegularExpression + type: string + type: object + x-kubernetes-validations: + - message: One or both of 'service' or 'method' must be + specified + rule: 'has(self.type) ? has(self.service) || has(self.method) + : true' + - message: service must only contain valid characters + (matching ^(?i)\.?[a-z_][a-z_0-9]*(\.[a-z_][a-z_0-9]*)*$) + rule: '(!has(self.type) || self.type == ''Exact'') && + has(self.service) ? self.service.matches(r"""^(?i)\.?[a-z_][a-z_0-9]*(\.[a-z_][a-z_0-9]*)*$"""): + true' + - message: method must only contain valid characters (matching + ^[A-Za-z_][A-Za-z_0-9]*$) + rule: '(!has(self.type) || self.type == ''Exact'') && + has(self.method) ? self.method.matches(r"""^[A-Za-z_][A-Za-z_0-9]*$"""): + true' + type: object + maxItems: 64 + type: array + name: + description: |- + Name is the name of the route rule. This name MUST be unique within a Route if it is set. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + sessionPersistence: + description: |- + SessionPersistence defines and configures session persistence + for the route rule. + + Support: Extended + properties: + absoluteTimeout: + description: |- + AbsoluteTimeout defines the absolute timeout of the persistent + session. Once the AbsoluteTimeout duration has elapsed, the + session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + cookieConfig: + description: |- + CookieConfig provides configuration settings that are specific + to cookie-based session persistence. + + Support: Core + properties: + lifetimeType: + default: Session + description: |- + LifetimeType specifies whether the cookie has a permanent or + session-based lifetime. A permanent cookie persists until its + specified expiry time, defined by the Expires or Max-Age cookie + attributes, while a session cookie is deleted when the current + session ends. + + When set to "Permanent", AbsoluteTimeout indicates the + cookie's lifetime via the Expires or Max-Age cookie attributes + and is required. + + When set to "Session", AbsoluteTimeout indicates the + absolute lifetime of the cookie tracked by the gateway and + is optional. + + Defaults to "Session". + + Support: Core for "Session" type + + Support: Extended for "Permanent" type + enum: + - Permanent + - Session + type: string + type: object + idleTimeout: + description: |- + IdleTimeout defines the idle timeout of the persistent session. + Once the session has been idle for more than the specified + IdleTimeout duration, the session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + sessionName: + description: |- + SessionName defines the name of the persistent session token + which may be reflected in the cookie or the header. Users + should avoid reusing session names to prevent unintended + consequences, such as rejection or unpredictable behavior. + + Support: Implementation-specific + maxLength: 128 + type: string + type: + default: Cookie + description: |- + Type defines the type of session persistence such as through + the use a header or cookie. Defaults to cookie based session + persistence. + + Support: Core for "Cookie" type + + Support: Extended for "Header" type + enum: + - Cookie + - Header + type: string + type: object + x-kubernetes-validations: + - message: AbsoluteTimeout must be specified when cookie lifetimeType + is Permanent + rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType) + || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)' + type: object + maxItems: 16 + type: array + x-kubernetes-validations: + - message: While 16 rules and 64 matches per rule are allowed, the + total number of matches across all rules in a route must be less + than 128 + rule: '(self.size() > 0 ? (has(self[0].matches) ? self[0].matches.size() + : 0) : 0) + (self.size() > 1 ? (has(self[1].matches) ? self[1].matches.size() + : 0) : 0) + (self.size() > 2 ? (has(self[2].matches) ? self[2].matches.size() + : 0) : 0) + (self.size() > 3 ? (has(self[3].matches) ? self[3].matches.size() + : 0) : 0) + (self.size() > 4 ? (has(self[4].matches) ? self[4].matches.size() + : 0) : 0) + (self.size() > 5 ? (has(self[5].matches) ? self[5].matches.size() + : 0) : 0) + (self.size() > 6 ? (has(self[6].matches) ? self[6].matches.size() + : 0) : 0) + (self.size() > 7 ? (has(self[7].matches) ? self[7].matches.size() + : 0) : 0) + (self.size() > 8 ? (has(self[8].matches) ? self[8].matches.size() + : 0) : 0) + (self.size() > 9 ? (has(self[9].matches) ? self[9].matches.size() + : 0) : 0) + (self.size() > 10 ? (has(self[10].matches) ? self[10].matches.size() + : 0) : 0) + (self.size() > 11 ? (has(self[11].matches) ? self[11].matches.size() + : 0) : 0) + (self.size() > 12 ? (has(self[12].matches) ? self[12].matches.size() + : 0) : 0) + (self.size() > 13 ? (has(self[13].matches) ? self[13].matches.size() + : 0) : 0) + (self.size() > 14 ? (has(self[14].matches) ? self[14].matches.size() + : 0) : 0) + (self.size() > 15 ? (has(self[15].matches) ? self[15].matches.size() + : 0) : 0) <= 128' + - message: Rule name must be unique within the route + rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) + && l1.name == l2.name)) + type: object + status: + description: Status defines the current state of GRPCRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_httproutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: httproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: HTTPRoute + listKind: HTTPRouteList + plural: httproutes + singular: httproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + HTTPRoute provides a way to route HTTP requests. This includes the capability + to match requests by hostname, path, header, or query param. Filters can be + used to specify additional processing steps. Backends specify where matching + requests should be routed. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of HTTPRoute. + properties: + hostnames: + description: |- + Hostnames defines a set of hostnames that should match against the HTTP Host + header to select a HTTPRoute used to process the request. Implementations + MUST ignore any port value specified in the HTTP Host header while + performing a match and (absent of any applicable header modification + configuration) MUST forward this header unmodified to the backend. + + Valid values for Hostnames are determined by RFC 1123 definition of a + hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + If a hostname is specified by both the Listener and HTTPRoute, there + must be at least one intersecting hostname for the HTTPRoute to be + attached to the Listener. For example: + + * A Listener with `test.example.com` as the hostname matches HTTPRoutes + that have either not specified any hostnames, or have specified at + least one of `test.example.com` or `*.example.com`. + * A Listener with `*.example.com` as the hostname matches HTTPRoutes + that have either not specified any hostnames or have specified at least + one hostname that matches the Listener hostname. For example, + `*.example.com`, `test.example.com`, and `foo.test.example.com` would + all match. On the other hand, `example.com` and `test.example.net` would + not match. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + + If both the Listener and HTTPRoute have specified hostnames, any + HTTPRoute hostnames that do not match the Listener hostname MUST be + ignored. For example, if a Listener specified `*.example.com`, and the + HTTPRoute specified `test.example.com` and `test.example.net`, + `test.example.net` must not be considered for a match. + + If both the Listener and HTTPRoute have specified hostnames, and none + match with the criteria above, then the HTTPRoute is not accepted. The + implementation must raise an 'Accepted' Condition with a status of + `False` in the corresponding RouteParentStatus. + + In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. + overlapping wildcard matching and exact matching hostnames), precedence must + be given to rules from the HTTPRoute with the largest number of: + + * Characters in a matching non-wildcard hostname. + * Characters in a matching hostname. + + If ties exist across multiple Routes, the matching precedence rules for + HTTPRouteMatches takes over. + + Support: Core + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) + rules: + default: + - matches: + - path: + type: PathPrefix + value: / + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: |- + HTTPRouteRule defines semantics for matching an HTTP request based on + conditions (matches), processing it (filters), and forwarding the request to + an API object (backendRefs). + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. + + Failure behavior here depends on how many BackendRefs are specified and + how many are invalid. + + If *all* entries in BackendRefs are invalid, and there are also no filters + specified in this route rule, *all* traffic which matches this rule MUST + receive a 500 status code. + + See the HTTPBackendRef definition for the rules about what makes a single + HTTPBackendRef invalid. + + When a HTTPBackendRef is invalid, 500 status codes MUST be returned for + requests that would have otherwise been routed to an invalid backend. If + multiple backends are specified, and some are invalid, the proportion of + requests that would otherwise have been routed to an invalid backend + MUST receive a 500 status code. + + For example, if two backends are specified with equal weights, and one is + invalid, 50 percent of traffic must receive a 500. Implementations may + choose how that 50 percent is determined. + + When a HTTPBackendRef refers to a Service that has no ready endpoints, + implementations SHOULD return a 503 for requests to that backend instead. + If an implementation chooses to do this, all of the above rules for 500 responses + MUST also apply for responses that return a 503. + + Support: Core for Kubernetes Service + + Support: Extended for Kubernetes ServiceImport + + Support: Implementation-specific for any other resource + + Support for weight: Core + items: + description: |- + HTTPBackendRef defines how a HTTPRoute forwards a HTTP request. + + Note that when a namespace different than the local namespace is specified, a + ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + + When the BackendRef points to a Kubernetes Service, implementations SHOULD + honor the appProtocol field if it is set for the target Service Port. + + Implementations supporting appProtocol SHOULD recognize the Kubernetes + Standard Application Protocols defined in KEP-3726. + + If a Service appProtocol isn't specified, an implementation MAY infer the + backend protocol through its own means. Implementations MAY infer the + protocol from the Route type referring to the backend Service. + + If a Route is not able to send traffic to the backend using the specified + protocol then the backend is considered invalid. Implementations MUST set the + "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. + properties: + filters: + description: |- + Filters defined at this level should be executed if and only if the + request is being forwarded to the backend defined here. + + Support: Implementation-specific (For broader support of filters, use the + Filters field in HTTPRouteRule.) + items: + description: |- + HTTPRouteFilter defines processing steps that must be completed during the + request or response lifecycle. HTTPRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + cors: + description: |- + CORS defines a schema for a filter that responds to the + cross-origin request based on HTTP response header. + + Support: Extended + properties: + allowCredentials: + description: |- + AllowCredentials indicates whether the actual cross-origin request allows + to include credentials. + + The only valid value for the `Access-Control-Allow-Credentials` response + header is true (case-sensitive). + + If the credentials are not allowed in cross-origin requests, the gateway + will omit the header `Access-Control-Allow-Credentials` entirely rather + than setting its value to false. + + Support: Extended + enum: + - true + type: boolean + allowHeaders: + description: |- + AllowHeaders indicates which HTTP request headers are supported for + accessing the requested resource. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Allow-Headers` + response header are separated by a comma (","). + + When the `AllowHeaders` field is configured with one or more headers, the + gateway must return the `Access-Control-Allow-Headers` response header + which value is present in the `AllowHeaders` field. + + If any header name in the `Access-Control-Request-Headers` request header + is not included in the list of header names specified by the response + header `Access-Control-Allow-Headers`, it will present an error on the + client side. + + If any header name in the `Access-Control-Allow-Headers` response header + does not recognize by the client, it will also occur an error on the + client side. + + A wildcard indicates that the requests with all HTTP headers are allowed. + The `Access-Control-Allow-Headers` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowHeaders` field + specified with the `*` wildcard, the gateway must specify one or more + HTTP headers in the value of the `Access-Control-Allow-Headers` response + header. The value of the header `Access-Control-Allow-Headers` is same as + the `Access-Control-Request-Headers` header provided by the client. If + the header `Access-Control-Request-Headers` is not included in the + request, the gateway will omit the `Access-Control-Allow-Headers` + response header, instead of specifying the `*` wildcard. A Gateway + implementation may choose to add implementation-specific default headers. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + allowMethods: + description: |- + AllowMethods indicates which HTTP methods are supported for accessing the + requested resource. + + Valid values are any method defined by RFC9110, along with the special + value `*`, which represents all HTTP methods are allowed. + + Method names are case sensitive, so these values are also case-sensitive. + (See https://www.rfc-editor.org/rfc/rfc2616#section-5.1.1) + + Multiple method names in the value of the `Access-Control-Allow-Methods` + response header are separated by a comma (","). + + A CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`. + (See https://fetch.spec.whatwg.org/#cors-safelisted-method) The + CORS-safelisted methods are always allowed, regardless of whether they + are specified in the `AllowMethods` field. + + When the `AllowMethods` field is configured with one or more methods, the + gateway must return the `Access-Control-Allow-Methods` response header + which value is present in the `AllowMethods` field. + + If the HTTP method of the `Access-Control-Request-Method` request header + is not included in the list of methods specified by the response header + `Access-Control-Allow-Methods`, it will present an error on the client + side. + + The `Access-Control-Allow-Methods` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowMethods` field + specified with the `*` wildcard, the gateway must specify one HTTP method + in the value of the Access-Control-Allow-Methods response header. The + value of the header `Access-Control-Allow-Methods` is same as the + `Access-Control-Request-Method` header provided by the client. If the + header `Access-Control-Request-Method` is not included in the request, + the gateway will omit the `Access-Control-Allow-Methods` response header, + instead of specifying the `*` wildcard. A Gateway implementation may + choose to add implementation-specific default methods. + + Support: Extended + items: + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + - '*' + type: string + maxItems: 9 + type: array + x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowMethods cannot contain '*' alongside + other methods + rule: '!(''*'' in self && self.size() > 1)' + allowOrigins: + description: |- + AllowOrigins indicates whether the response can be shared with requested + resource from the given `Origin`. + + The `Origin` consists of a scheme and a host, with an optional port, and + takes the form `://(:)`. + + Valid values for scheme are: `http` and `https`. + + Valid values for port are any integer between 1 and 65535 (the list of + available TCP/UDP ports). Note that, if not included, port `80` is + assumed for `http` scheme origins, and port `443` is assumed for `https` + origins. This may affect origin matching. + + The host part of the origin may contain the wildcard character `*`. These + wildcard characters behave as follows: + + * `*` is a greedy match to the _left_, including any number of + DNS labels to the left of its position. This also means that + `*` will include any number of period `.` characters to the + left of its position. + * A wildcard by itself matches all hosts. + + An origin value that includes _only_ the `*` character indicates requests + from all `Origin`s are allowed. + + When the `AllowOrigins` field is configured with multiple origins, it + means the server supports clients from multiple origins. If the request + `Origin` matches the configured allowed origins, the gateway must return + the given `Origin` and sets value of the header + `Access-Control-Allow-Origin` same as the `Origin` header provided by the + client. + + The status code of a successful response to a "preflight" request is + always an OK status (i.e., 204 or 200). + + If the request `Origin` does not match the configured allowed origins, + the gateway returns 204/200 response but doesn't set the relevant + cross-origin response headers. Alternatively, the gateway responds with + 403 status to the "preflight" request is denied, coupled with omitting + the CORS headers. The cross-origin request fails on the client side. + Therefore, the client doesn't attempt the actual cross-origin request. + + The `Access-Control-Allow-Origin` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowOrigins` field + specified with the `*` wildcard, the gateway must return a single origin + in the value of the `Access-Control-Allow-Origin` response header, + instead of specifying the `*` wildcard. The value of the header + `Access-Control-Allow-Origin` is same as the `Origin` header provided by + the client. + + Support: Extended + items: + description: |- + The AbsoluteURI MUST NOT be a relative URI, and it MUST follow the URI syntax and + encoding rules specified in RFC3986. The AbsoluteURI MUST include both a + scheme (e.g., "http" or "spiffe") and a scheme-specific-part. URIs that + include an authority MUST include a fully qualified domain name or + IP address as the host. + maxLength: 253 + minLength: 1 + pattern: ^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))? + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + exposeHeaders: + description: |- + ExposeHeaders indicates which HTTP response headers can be exposed + to client-side scripts in response to a cross-origin request. + + A CORS-safelisted response header is an HTTP header in a CORS response + that it is considered safe to expose to the client scripts. + The CORS-safelisted response headers include the following headers: + `Cache-Control` + `Content-Language` + `Content-Length` + `Content-Type` + `Expires` + `Last-Modified` + `Pragma` + (See https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name) + The CORS-safelisted response headers are exposed to client by default. + + When an HTTP header name is specified using the `ExposeHeaders` field, + this additional header will be exposed as part of the response to the + client. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Expose-Headers` + response header are separated by a comma (","). + + A wildcard indicates that the responses with all HTTP headers are exposed + to clients. The `Access-Control-Expose-Headers` response header can only + use `*` wildcard as value when the `AllowCredentials` field is + unspecified. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + maxAge: + default: 5 + description: |- + MaxAge indicates the duration (in seconds) for the client to cache the + results of a "preflight" request. + + The information provided by the `Access-Control-Allow-Methods` and + `Access-Control-Allow-Headers` response headers can be cached by the + client until the time specified by `Access-Control-Max-Age` elapses. + + The default value of `Access-Control-Max-Age` response header is 5 + (seconds). + format: int32 + minimum: 1 + type: integer + type: object + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + This filter can be used multiple times within the same rule. + + Support: Implementation-specific + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal + to denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be + specified in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + requestRedirect: + description: |- + RequestRedirect defines a schema for a filter that responds to the + request with an HTTP redirection. + + Support: Core + properties: + hostname: + description: |- + Hostname is the hostname to be used in the value of the `Location` + header in the response. + When empty, the hostname in the `Host` header of the request is used. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines parameters used to modify the path of the incoming request. + The modified path is then used to construct the `Location` header. When + empty, the request path is used as-is. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified + when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? + has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when + replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type + == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified + when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' + ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' + when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + port: + description: |- + Port is the port to be used in the value of the `Location` + header in the response. + + If no port is specified, the redirect port MUST be derived using the + following rules: + + * If redirect scheme is not-empty, the redirect port MUST be the well-known + port associated with the redirect scheme. Specifically "http" to port 80 + and "https" to port 443. If the redirect scheme does not have a + well-known port, the listener port of the Gateway SHOULD be used. + * If redirect scheme is empty, the redirect port MUST be the Gateway + Listener port. + + Implementations SHOULD NOT add the port number in the 'Location' + header in the following cases: + + * A Location header that will use HTTP (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 80. + * A Location header that will use HTTPS (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 443. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: |- + Scheme is the scheme to be used in the value of the `Location` header in + the response. When empty, the scheme of the request is used. + + Scheme redirects can affect the port of the redirect, for more information, + refer to the documentation for the port field of this filter. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Extended + enum: + - http + - https + type: string + statusCode: + default: 302 + description: |- + StatusCode is the HTTP status code to be used in response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Core + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations must support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by + specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` should be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + - CORS + type: string + urlRewrite: + description: |- + URLRewrite defines a schema for a filter that modifies a request during forwarding. + + Support: Extended + properties: + hostname: + description: |- + Hostname is the value to be used to replace the Host header value during + forwarding. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines a path rewrite. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified + when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? + has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when + replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type + == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified + when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' + ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' + when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil + if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type + != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type + == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil + if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type + != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for + RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == + ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the + filter.type is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != + ''RequestRedirect'')' + - message: filter.requestRedirect must be specified + for RequestRedirect filter.type + rule: '!(!has(self.requestRedirect) && self.type == + ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type + is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite + filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for + ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + - message: filter.cors must be nil if the filter.type + is not CORS + rule: '!(has(self.cors) && self.type != ''CORS'')' + - message: filter.cors must be specified for CORS filter.type + rule: '!(!has(self.cors) && self.type == ''CORS'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') + && self.exists(f, f.type == ''URLRewrite''))' + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') + && self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() + <= 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() + <= 1 + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + filters: + description: |- + Filters define the filters that are applied to requests that match + this rule. + + Wherever possible, implementations SHOULD implement filters in the order + they are specified. + + Implementations MAY choose to implement this ordering strictly, rejecting + any combination or order of filters that cannot be supported. If implementations + choose a strict interpretation of filter ordering, they MUST clearly document + that behavior. + + To reject an invalid combination or order of filters, implementations SHOULD + consider the Route Rules with this configuration invalid. If all Route Rules + in a Route are invalid, the entire Route would be considered invalid. If only + a portion of Route Rules are invalid, implementations MUST set the + "PartiallyInvalid" condition for the Route. + + Conformance-levels at this level are defined based on the type of filter: + + - ALL core filters MUST be supported by all implementations. + - Implementers are encouraged to support extended filters. + - Implementation-specific custom filters have no API guarantees across + implementations. + + Specifying the same filter multiple times is not supported unless explicitly + indicated in the filter. + + All filters are expected to be compatible with each other except for the + URLRewrite and RequestRedirect filters, which may not be combined. If an + implementation cannot support other combinations of filters, they must clearly + document that limitation. In cases where incompatible or unsupported + filters are specified and cause the `Accepted` condition to be set to status + `False`, implementations may use the `IncompatibleFilters` reason to specify + this configuration error. + + Support: Core + items: + description: |- + HTTPRouteFilter defines processing steps that must be completed during the + request or response lifecycle. HTTPRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + cors: + description: |- + CORS defines a schema for a filter that responds to the + cross-origin request based on HTTP response header. + + Support: Extended + properties: + allowCredentials: + description: |- + AllowCredentials indicates whether the actual cross-origin request allows + to include credentials. + + The only valid value for the `Access-Control-Allow-Credentials` response + header is true (case-sensitive). + + If the credentials are not allowed in cross-origin requests, the gateway + will omit the header `Access-Control-Allow-Credentials` entirely rather + than setting its value to false. + + Support: Extended + enum: + - true + type: boolean + allowHeaders: + description: |- + AllowHeaders indicates which HTTP request headers are supported for + accessing the requested resource. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Allow-Headers` + response header are separated by a comma (","). + + When the `AllowHeaders` field is configured with one or more headers, the + gateway must return the `Access-Control-Allow-Headers` response header + which value is present in the `AllowHeaders` field. + + If any header name in the `Access-Control-Request-Headers` request header + is not included in the list of header names specified by the response + header `Access-Control-Allow-Headers`, it will present an error on the + client side. + + If any header name in the `Access-Control-Allow-Headers` response header + does not recognize by the client, it will also occur an error on the + client side. + + A wildcard indicates that the requests with all HTTP headers are allowed. + The `Access-Control-Allow-Headers` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowHeaders` field + specified with the `*` wildcard, the gateway must specify one or more + HTTP headers in the value of the `Access-Control-Allow-Headers` response + header. The value of the header `Access-Control-Allow-Headers` is same as + the `Access-Control-Request-Headers` header provided by the client. If + the header `Access-Control-Request-Headers` is not included in the + request, the gateway will omit the `Access-Control-Allow-Headers` + response header, instead of specifying the `*` wildcard. A Gateway + implementation may choose to add implementation-specific default headers. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + allowMethods: + description: |- + AllowMethods indicates which HTTP methods are supported for accessing the + requested resource. + + Valid values are any method defined by RFC9110, along with the special + value `*`, which represents all HTTP methods are allowed. + + Method names are case sensitive, so these values are also case-sensitive. + (See https://www.rfc-editor.org/rfc/rfc2616#section-5.1.1) + + Multiple method names in the value of the `Access-Control-Allow-Methods` + response header are separated by a comma (","). + + A CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`. + (See https://fetch.spec.whatwg.org/#cors-safelisted-method) The + CORS-safelisted methods are always allowed, regardless of whether they + are specified in the `AllowMethods` field. + + When the `AllowMethods` field is configured with one or more methods, the + gateway must return the `Access-Control-Allow-Methods` response header + which value is present in the `AllowMethods` field. + + If the HTTP method of the `Access-Control-Request-Method` request header + is not included in the list of methods specified by the response header + `Access-Control-Allow-Methods`, it will present an error on the client + side. + + The `Access-Control-Allow-Methods` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowMethods` field + specified with the `*` wildcard, the gateway must specify one HTTP method + in the value of the Access-Control-Allow-Methods response header. The + value of the header `Access-Control-Allow-Methods` is same as the + `Access-Control-Request-Method` header provided by the client. If the + header `Access-Control-Request-Method` is not included in the request, + the gateway will omit the `Access-Control-Allow-Methods` response header, + instead of specifying the `*` wildcard. A Gateway implementation may + choose to add implementation-specific default methods. + + Support: Extended + items: + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + - '*' + type: string + maxItems: 9 + type: array + x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowMethods cannot contain '*' alongside + other methods + rule: '!(''*'' in self && self.size() > 1)' + allowOrigins: + description: |- + AllowOrigins indicates whether the response can be shared with requested + resource from the given `Origin`. + + The `Origin` consists of a scheme and a host, with an optional port, and + takes the form `://(:)`. + + Valid values for scheme are: `http` and `https`. + + Valid values for port are any integer between 1 and 65535 (the list of + available TCP/UDP ports). Note that, if not included, port `80` is + assumed for `http` scheme origins, and port `443` is assumed for `https` + origins. This may affect origin matching. + + The host part of the origin may contain the wildcard character `*`. These + wildcard characters behave as follows: + + * `*` is a greedy match to the _left_, including any number of + DNS labels to the left of its position. This also means that + `*` will include any number of period `.` characters to the + left of its position. + * A wildcard by itself matches all hosts. + + An origin value that includes _only_ the `*` character indicates requests + from all `Origin`s are allowed. + + When the `AllowOrigins` field is configured with multiple origins, it + means the server supports clients from multiple origins. If the request + `Origin` matches the configured allowed origins, the gateway must return + the given `Origin` and sets value of the header + `Access-Control-Allow-Origin` same as the `Origin` header provided by the + client. + + The status code of a successful response to a "preflight" request is + always an OK status (i.e., 204 or 200). + + If the request `Origin` does not match the configured allowed origins, + the gateway returns 204/200 response but doesn't set the relevant + cross-origin response headers. Alternatively, the gateway responds with + 403 status to the "preflight" request is denied, coupled with omitting + the CORS headers. The cross-origin request fails on the client side. + Therefore, the client doesn't attempt the actual cross-origin request. + + The `Access-Control-Allow-Origin` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowOrigins` field + specified with the `*` wildcard, the gateway must return a single origin + in the value of the `Access-Control-Allow-Origin` response header, + instead of specifying the `*` wildcard. The value of the header + `Access-Control-Allow-Origin` is same as the `Origin` header provided by + the client. + + Support: Extended + items: + description: |- + The AbsoluteURI MUST NOT be a relative URI, and it MUST follow the URI syntax and + encoding rules specified in RFC3986. The AbsoluteURI MUST include both a + scheme (e.g., "http" or "spiffe") and a scheme-specific-part. URIs that + include an authority MUST include a fully qualified domain name or + IP address as the host. + maxLength: 253 + minLength: 1 + pattern: ^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))? + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + exposeHeaders: + description: |- + ExposeHeaders indicates which HTTP response headers can be exposed + to client-side scripts in response to a cross-origin request. + + A CORS-safelisted response header is an HTTP header in a CORS response + that it is considered safe to expose to the client scripts. + The CORS-safelisted response headers include the following headers: + `Cache-Control` + `Content-Language` + `Content-Length` + `Content-Type` + `Expires` + `Last-Modified` + `Pragma` + (See https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name) + The CORS-safelisted response headers are exposed to client by default. + + When an HTTP header name is specified using the `ExposeHeaders` field, + this additional header will be exposed as part of the response to the + client. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Expose-Headers` + response header are separated by a comma (","). + + A wildcard indicates that the responses with all HTTP headers are exposed + to clients. The `Access-Control-Expose-Headers` response header can only + use `*` wildcard as value when the `AllowCredentials` field is + unspecified. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + maxAge: + default: 5 + description: |- + MaxAge indicates the duration (in seconds) for the client to cache the + results of a "preflight" request. + + The information provided by the `Access-Control-Allow-Methods` and + `Access-Control-Allow-Headers` response headers can be cached by the + client until the time specified by `Access-Control-Max-Age` elapses. + + The default value of `Access-Control-Max-Age` response header is 5 + (seconds). + format: int32 + minimum: 1 + type: integer + type: object + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + This filter can be used multiple times within the same rule. + + Support: Implementation-specific + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal to + denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be specified + in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + requestRedirect: + description: |- + RequestRedirect defines a schema for a filter that responds to the + request with an HTTP redirection. + + Support: Core + properties: + hostname: + description: |- + Hostname is the hostname to be used in the value of the `Location` + header in the response. + When empty, the hostname in the `Host` header of the request is used. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines parameters used to modify the path of the incoming request. + The modified path is then used to construct the `Location` header. When + empty, the request path is used as-is. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when + type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) + : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath + is set + rule: 'has(self.replaceFullPath) ? self.type == + ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when + type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) + : true' + - message: type must be 'ReplacePrefixMatch' when + replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + port: + description: |- + Port is the port to be used in the value of the `Location` + header in the response. + + If no port is specified, the redirect port MUST be derived using the + following rules: + + * If redirect scheme is not-empty, the redirect port MUST be the well-known + port associated with the redirect scheme. Specifically "http" to port 80 + and "https" to port 443. If the redirect scheme does not have a + well-known port, the listener port of the Gateway SHOULD be used. + * If redirect scheme is empty, the redirect port MUST be the Gateway + Listener port. + + Implementations SHOULD NOT add the port number in the 'Location' + header in the following cases: + + * A Location header that will use HTTP (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 80. + * A Location header that will use HTTPS (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 443. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: |- + Scheme is the scheme to be used in the value of the `Location` header in + the response. When empty, the scheme of the request is used. + + Scheme redirects can affect the port of the redirect, for more information, + refer to the documentation for the port field of this filter. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Extended + enum: + - http + - https + type: string + statusCode: + default: 302 + description: |- + StatusCode is the HTTP status code to be used in response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Core + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations must support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by + specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` should be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + - CORS + type: string + urlRewrite: + description: |- + URLRewrite defines a schema for a filter that modifies a request during forwarding. + + Support: Extended + properties: + hostname: + description: |- + Hostname is the value to be used to replace the Host header value during + forwarding. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines a path rewrite. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when + type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) + : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath + is set + rule: 'has(self.replaceFullPath) ? self.type == + ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when + type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) + : true' + - message: type must be 'ReplacePrefixMatch' when + replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the + filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != + ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == + ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the + filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != + ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror + filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the filter.type + is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')' + - message: filter.requestRedirect must be specified for RequestRedirect + filter.type + rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type + is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite + filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef + filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + - message: filter.cors must be nil if the filter.type is not + CORS + rule: '!(has(self.cors) && self.type != ''CORS'')' + - message: filter.cors must be specified for CORS filter.type + rule: '!(!has(self.cors) && self.type == ''CORS'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') && + self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() <= + 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() <= 1 + matches: + default: + - path: + type: PathPrefix + value: / + description: |- + Matches define conditions used for matching the rule against incoming + HTTP requests. Each match is independent, i.e. this rule will be matched + if **any** one of the matches is satisfied. + + For example, take the following matches configuration: + + ``` + matches: + - path: + value: "/foo" + headers: + - name: "version" + value: "v2" + - path: + value: "/v2/foo" + ``` + + For a request to match against this rule, a request must satisfy + EITHER of the two conditions: + + - path prefixed with `/foo` AND contains the header `version: v2` + - path prefix of `/v2/foo` + + See the documentation for HTTPRouteMatch on how to specify multiple + match conditions that should be ANDed together. + + If no matches are specified, the default is a prefix + path match on "/", which has the effect of matching every + HTTP request. + + Proxy or Load Balancer routing configuration generated from HTTPRoutes + MUST prioritize matches based on the following criteria, continuing on + ties. Across all rules specified on applicable Routes, precedence must be + given to the match having: + + * "Exact" path match. + * "Prefix" path match with largest number of characters. + * Method match. + * Largest number of header matches. + * Largest number of query param matches. + + Note: The precedence of RegularExpression path matches are implementation-specific. + + If ties still exist across multiple Routes, matching precedence MUST be + determined in order of the following criteria, continuing on ties: + + * The oldest Route based on creation timestamp. + * The Route appearing first in alphabetical order by + "{namespace}/{name}". + + If ties still exist within an HTTPRoute, matching precedence MUST be granted + to the FIRST matching rule (in list order) with a match meeting the above + criteria. + + When no rules matching a request have been successfully attached to the + parent a request is coming from, a HTTP 404 status code MUST be returned. + items: + description: "HTTPRouteMatch defines the predicate used to + match requests to a given\naction. Multiple match types + are ANDed together, i.e. the match will\nevaluate to true + only if all conditions are satisfied.\n\nFor example, the + match below will match a HTTP request only if its path\nstarts + with `/foo` AND it contains the `version: v1` header:\n\n```\nmatch:\n\n\tpath:\n\t + \ value: \"/foo\"\n\theaders:\n\t- name: \"version\"\n\t + \ value \"v1\"\n\n```" + properties: + headers: + description: |- + Headers specifies HTTP request header matchers. Multiple match values are + ANDed together, meaning, a request must match all the specified headers + to select the route. + items: + description: |- + HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request + headers. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + + When a header is repeated in an HTTP request, it is + implementation-specific behavior as to how this is represented. + Generally, proxies should follow the guidance from the RFC: + https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding + processing a repeated header, with special handling for "Set-Cookie". + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the header. + + Support: Core (Exact) + + Support: Implementation-specific (RegularExpression) + + Since RegularExpression HeaderMatchType has implementation-specific + conformance, implementations can support POSIX, PCRE or any other dialects + of regular expressions. Please read the implementation's documentation to + determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP Header to + be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: |- + Method specifies HTTP method matcher. + When specified, this route will be matched only if the request has the + specified method. + + Support: Extended + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: |- + Path specifies a HTTP request path matcher. If this field is not + specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: |- + Type specifies how to match against the path Value. + + Support: Core (Exact, PathPrefix) + + Support: Implementation-specific (RegularExpression) + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path to match against. + maxLength: 1024 + type: string + type: object + x-kubernetes-validations: + - message: value must be an absolute path and start with + '/' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.startsWith(''/'') + : true' + - message: must not contain '//' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''//'') + : true' + - message: must not contain '/./' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/./'') + : true' + - message: must not contain '/../' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/../'') + : true' + - message: must not contain '%2f' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2f'') + : true' + - message: must not contain '%2F' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2F'') + : true' + - message: must not contain '#' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''#'') + : true' + - message: must not end with '/..' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/..'') + : true' + - message: must not end with '/.' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/.'') + : true' + - message: type must be one of ['Exact', 'PathPrefix', + 'RegularExpression'] + rule: self.type in ['Exact','PathPrefix'] || self.type + == 'RegularExpression' + - message: must only contain valid characters (matching + ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$) + for types ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.matches(r"""^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$""") + : true' + queryParams: + description: |- + QueryParams specifies HTTP query parameter matchers. Multiple match + values are ANDed together, meaning, a request must match all the + specified query parameters to select the route. + + Support: Extended + items: + description: |- + HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP + query parameters. + properties: + name: + description: |- + Name is the name of the HTTP query param to be matched. This must be an + exact string match. (See + https://tools.ietf.org/html/rfc7230#section-2.7.3). + + If multiple entries specify equivalent query param names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent query param name MUST be ignored. + + If a query param is repeated in an HTTP request, the behavior is + purposely left undefined, since different data planes have different + capabilities. However, it is *recommended* that implementations should + match against the first value of the param if the data plane supports it, + as this behavior is expected in other load balancing contexts outside of + the Gateway API. + + Users SHOULD NOT route traffic based on repeated query params to guard + themselves against potential differences in the implementations. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the query parameter. + + Support: Extended (Exact) + + Support: Implementation-specific (RegularExpression) + + Since RegularExpression QueryParamMatchType has Implementation-specific + conformance, implementations can support POSIX, PCRE or any other + dialects of regular expressions. Please read the implementation's + documentation to determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP query param + to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 64 + type: array + name: + description: |- + Name is the name of the route rule. This name MUST be unique within a Route if it is set. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + retry: + description: |- + Retry defines the configuration for when to retry an HTTP request. + + Support: Extended + properties: + attempts: + description: |- + Attempts specifies the maximum number of times an individual request + from the gateway to a backend should be retried. + + If the maximum number of retries has been attempted without a successful + response from the backend, the Gateway MUST return an error. + + When this field is unspecified, the number of times to attempt to retry + a backend request is implementation-specific. + + Support: Extended + type: integer + backoff: + description: |- + Backoff specifies the minimum duration a Gateway should wait between + retry attempts and is represented in Gateway API Duration formatting. + + For example, setting the `rules[].retry.backoff` field to the value + `100ms` will cause a backend request to first be retried approximately + 100 milliseconds after timing out or receiving a response code configured + to be retryable. + + An implementation MAY use an exponential or alternative backoff strategy + for subsequent retry attempts, MAY cap the maximum backoff duration to + some amount greater than the specified minimum, and MAY add arbitrary + jitter to stagger requests, as long as unsuccessful backend requests are + not retried before the configured minimum duration. + + If a Request timeout (`rules[].timeouts.request`) is configured on the + route, the entire duration of the initial request and any retry attempts + MUST not exceed the Request timeout duration. If any retry attempts are + still in progress when the Request timeout duration has been reached, + these SHOULD be canceled if possible and the Gateway MUST immediately + return a timeout error. + + If a BackendRequest timeout (`rules[].timeouts.backendRequest`) is + configured on the route, any retry attempts which reach the configured + BackendRequest timeout duration without a response SHOULD be canceled if + possible and the Gateway should wait for at least the specified backoff + duration before attempting to retry the backend request again. + + If a BackendRequest timeout is _not_ configured on the route, retry + attempts MAY time out after an implementation default duration, or MAY + remain pending until a configured Request timeout or implementation + default duration for total request time is reached. + + When this field is unspecified, the time to wait between retry attempts + is implementation-specific. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + codes: + description: |- + Codes defines the HTTP response status codes for which a backend request + should be retried. + + Support: Extended + items: + description: |- + HTTPRouteRetryStatusCode defines an HTTP response status code for + which a backend request should be retried. + + Implementations MUST support the following status codes as retryable: + + * 500 + * 502 + * 503 + * 504 + + Implementations MAY support specifying additional discrete values in the + 500-599 range. + + Implementations MAY support specifying discrete values in the 400-499 range, + which are often inadvisable to retry. + maximum: 599 + minimum: 400 + type: integer + type: array + type: object + sessionPersistence: + description: |- + SessionPersistence defines and configures session persistence + for the route rule. + + Support: Extended + properties: + absoluteTimeout: + description: |- + AbsoluteTimeout defines the absolute timeout of the persistent + session. Once the AbsoluteTimeout duration has elapsed, the + session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + cookieConfig: + description: |- + CookieConfig provides configuration settings that are specific + to cookie-based session persistence. + + Support: Core + properties: + lifetimeType: + default: Session + description: |- + LifetimeType specifies whether the cookie has a permanent or + session-based lifetime. A permanent cookie persists until its + specified expiry time, defined by the Expires or Max-Age cookie + attributes, while a session cookie is deleted when the current + session ends. + + When set to "Permanent", AbsoluteTimeout indicates the + cookie's lifetime via the Expires or Max-Age cookie attributes + and is required. + + When set to "Session", AbsoluteTimeout indicates the + absolute lifetime of the cookie tracked by the gateway and + is optional. + + Defaults to "Session". + + Support: Core for "Session" type + + Support: Extended for "Permanent" type + enum: + - Permanent + - Session + type: string + type: object + idleTimeout: + description: |- + IdleTimeout defines the idle timeout of the persistent session. + Once the session has been idle for more than the specified + IdleTimeout duration, the session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + sessionName: + description: |- + SessionName defines the name of the persistent session token + which may be reflected in the cookie or the header. Users + should avoid reusing session names to prevent unintended + consequences, such as rejection or unpredictable behavior. + + Support: Implementation-specific + maxLength: 128 + type: string + type: + default: Cookie + description: |- + Type defines the type of session persistence such as through + the use a header or cookie. Defaults to cookie based session + persistence. + + Support: Core for "Cookie" type + + Support: Extended for "Header" type + enum: + - Cookie + - Header + type: string + type: object + x-kubernetes-validations: + - message: AbsoluteTimeout must be specified when cookie lifetimeType + is Permanent + rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType) + || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)' + timeouts: + description: |- + Timeouts defines the timeouts that can be configured for an HTTP request. + + Support: Extended + properties: + backendRequest: + description: |- + BackendRequest specifies a timeout for an individual request from the gateway + to a backend. This covers the time from when the request first starts being + sent from the gateway to when the full response has been received from the backend. + + Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout + completely. Implementations that cannot completely disable the timeout MUST + instead interpret the zero duration as the longest possible value to which + the timeout can be set. + + An entire client HTTP transaction with a gateway, covered by the Request timeout, + may result in more than one call from the gateway to the destination backend, + for example, if automatic retries are supported. + + The value of BackendRequest must be a Gateway API Duration string as defined by + GEP-2257. When this field is unspecified, its behavior is implementation-specific; + when specified, the value of BackendRequest must be no more than the value of the + Request timeout (since the Request timeout encompasses the BackendRequest timeout). + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + request: + description: |- + Request specifies the maximum duration for a gateway to respond to an HTTP request. + If the gateway has not been able to respond before this deadline is met, the gateway + MUST return a timeout error. + + For example, setting the `rules.timeouts.request` field to the value `10s` in an + `HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds + to complete. + + Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout + completely. Implementations that cannot completely disable the timeout MUST + instead interpret the zero duration as the longest possible value to which + the timeout can be set. + + This timeout is intended to cover as close to the whole request-response transaction + as possible although an implementation MAY choose to start the timeout after the entire + request stream has been received instead of immediately after the transaction is + initiated by the client. + + The value of Request is a Gateway API Duration string as defined by GEP-2257. When this + field is unspecified, request timeout behavior is implementation-specific. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + x-kubernetes-validations: + - message: backendRequest timeout cannot be longer than request + timeout + rule: '!(has(self.request) && has(self.backendRequest) && + duration(self.request) != duration(''0s'') && duration(self.backendRequest) + > duration(self.request))' + type: object + x-kubernetes-validations: + - message: RequestRedirect filter must not be used together with + backendRefs + rule: '(has(self.backendRefs) && size(self.backendRefs) > 0) ? + (!has(self.filters) || self.filters.all(f, !has(f.requestRedirect))): + true' + - message: When using RequestRedirect filter with path.replacePrefixMatch, + exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.requestRedirect) + && has(f.requestRedirect.path) && f.requestRedirect.path.type + == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) + ? ((size(self.matches) != 1 || !has(self.matches[0].path) || + self.matches[0].path.type != ''PathPrefix'') ? false : true) + : true' + - message: When using URLRewrite filter with path.replacePrefixMatch, + exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.urlRewrite) + && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' + && has(f.urlRewrite.path.replacePrefixMatch))) ? ((size(self.matches) + != 1 || !has(self.matches[0].path) || self.matches[0].path.type + != ''PathPrefix'') ? false : true) : true' + - message: Within backendRefs, when using RequestRedirect filter + with path.replacePrefixMatch, exactly one PathPrefix match must + be specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, + (has(b.filters) && b.filters.exists_one(f, has(f.requestRedirect) + && has(f.requestRedirect.path) && f.requestRedirect.path.type + == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) + )) ? ((size(self.matches) != 1 || !has(self.matches[0].path) + || self.matches[0].path.type != ''PathPrefix'') ? false : true) + : true' + - message: Within backendRefs, When using URLRewrite filter with + path.replacePrefixMatch, exactly one PathPrefix match must be + specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, + (has(b.filters) && b.filters.exists_one(f, has(f.urlRewrite) + && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' + && has(f.urlRewrite.path.replacePrefixMatch))) )) ? ((size(self.matches) + != 1 || !has(self.matches[0].path) || self.matches[0].path.type + != ''PathPrefix'') ? false : true) : true' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: While 16 rules and 64 matches per rule are allowed, the + total number of matches across all rules in a route must be less + than 128 + rule: '(self.size() > 0 ? self[0].matches.size() : 0) + (self.size() + > 1 ? self[1].matches.size() : 0) + (self.size() > 2 ? self[2].matches.size() + : 0) + (self.size() > 3 ? self[3].matches.size() : 0) + (self.size() + > 4 ? self[4].matches.size() : 0) + (self.size() > 5 ? self[5].matches.size() + : 0) + (self.size() > 6 ? self[6].matches.size() : 0) + (self.size() + > 7 ? self[7].matches.size() : 0) + (self.size() > 8 ? self[8].matches.size() + : 0) + (self.size() > 9 ? self[9].matches.size() : 0) + (self.size() + > 10 ? self[10].matches.size() : 0) + (self.size() > 11 ? self[11].matches.size() + : 0) + (self.size() > 12 ? self[12].matches.size() : 0) + (self.size() + > 13 ? self[13].matches.size() : 0) + (self.size() > 14 ? self[14].matches.size() + : 0) + (self.size() > 15 ? self[15].matches.size() : 0) <= 128' + - message: Rule name must be unique within the route + rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) + && l1.name == l2.name)) + type: object + status: + description: Status defines the current state of HTTPRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: |- + HTTPRoute provides a way to route HTTP requests. This includes the capability + to match requests by hostname, path, header, or query param. Filters can be + used to specify additional processing steps. Backends specify where matching + requests should be routed. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of HTTPRoute. + properties: + hostnames: + description: |- + Hostnames defines a set of hostnames that should match against the HTTP Host + header to select a HTTPRoute used to process the request. Implementations + MUST ignore any port value specified in the HTTP Host header while + performing a match and (absent of any applicable header modification + configuration) MUST forward this header unmodified to the backend. + + Valid values for Hostnames are determined by RFC 1123 definition of a + hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + If a hostname is specified by both the Listener and HTTPRoute, there + must be at least one intersecting hostname for the HTTPRoute to be + attached to the Listener. For example: + + * A Listener with `test.example.com` as the hostname matches HTTPRoutes + that have either not specified any hostnames, or have specified at + least one of `test.example.com` or `*.example.com`. + * A Listener with `*.example.com` as the hostname matches HTTPRoutes + that have either not specified any hostnames or have specified at least + one hostname that matches the Listener hostname. For example, + `*.example.com`, `test.example.com`, and `foo.test.example.com` would + all match. On the other hand, `example.com` and `test.example.net` would + not match. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + + If both the Listener and HTTPRoute have specified hostnames, any + HTTPRoute hostnames that do not match the Listener hostname MUST be + ignored. For example, if a Listener specified `*.example.com`, and the + HTTPRoute specified `test.example.com` and `test.example.net`, + `test.example.net` must not be considered for a match. + + If both the Listener and HTTPRoute have specified hostnames, and none + match with the criteria above, then the HTTPRoute is not accepted. The + implementation must raise an 'Accepted' Condition with a status of + `False` in the corresponding RouteParentStatus. + + In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. + overlapping wildcard matching and exact matching hostnames), precedence must + be given to rules from the HTTPRoute with the largest number of: + + * Characters in a matching non-wildcard hostname. + * Characters in a matching hostname. + + If ties exist across multiple Routes, the matching precedence rules for + HTTPRouteMatches takes over. + + Support: Core + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) + rules: + default: + - matches: + - path: + type: PathPrefix + value: / + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: |- + HTTPRouteRule defines semantics for matching an HTTP request based on + conditions (matches), processing it (filters), and forwarding the request to + an API object (backendRefs). + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. + + Failure behavior here depends on how many BackendRefs are specified and + how many are invalid. + + If *all* entries in BackendRefs are invalid, and there are also no filters + specified in this route rule, *all* traffic which matches this rule MUST + receive a 500 status code. + + See the HTTPBackendRef definition for the rules about what makes a single + HTTPBackendRef invalid. + + When a HTTPBackendRef is invalid, 500 status codes MUST be returned for + requests that would have otherwise been routed to an invalid backend. If + multiple backends are specified, and some are invalid, the proportion of + requests that would otherwise have been routed to an invalid backend + MUST receive a 500 status code. + + For example, if two backends are specified with equal weights, and one is + invalid, 50 percent of traffic must receive a 500. Implementations may + choose how that 50 percent is determined. + + When a HTTPBackendRef refers to a Service that has no ready endpoints, + implementations SHOULD return a 503 for requests to that backend instead. + If an implementation chooses to do this, all of the above rules for 500 responses + MUST also apply for responses that return a 503. + + Support: Core for Kubernetes Service + + Support: Extended for Kubernetes ServiceImport + + Support: Implementation-specific for any other resource + + Support for weight: Core + items: + description: |- + HTTPBackendRef defines how a HTTPRoute forwards a HTTP request. + + Note that when a namespace different than the local namespace is specified, a + ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + + When the BackendRef points to a Kubernetes Service, implementations SHOULD + honor the appProtocol field if it is set for the target Service Port. + + Implementations supporting appProtocol SHOULD recognize the Kubernetes + Standard Application Protocols defined in KEP-3726. + + If a Service appProtocol isn't specified, an implementation MAY infer the + backend protocol through its own means. Implementations MAY infer the + protocol from the Route type referring to the backend Service. + + If a Route is not able to send traffic to the backend using the specified + protocol then the backend is considered invalid. Implementations MUST set the + "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. + properties: + filters: + description: |- + Filters defined at this level should be executed if and only if the + request is being forwarded to the backend defined here. + + Support: Implementation-specific (For broader support of filters, use the + Filters field in HTTPRouteRule.) + items: + description: |- + HTTPRouteFilter defines processing steps that must be completed during the + request or response lifecycle. HTTPRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + cors: + description: |- + CORS defines a schema for a filter that responds to the + cross-origin request based on HTTP response header. + + Support: Extended + properties: + allowCredentials: + description: |- + AllowCredentials indicates whether the actual cross-origin request allows + to include credentials. + + The only valid value for the `Access-Control-Allow-Credentials` response + header is true (case-sensitive). + + If the credentials are not allowed in cross-origin requests, the gateway + will omit the header `Access-Control-Allow-Credentials` entirely rather + than setting its value to false. + + Support: Extended + enum: + - true + type: boolean + allowHeaders: + description: |- + AllowHeaders indicates which HTTP request headers are supported for + accessing the requested resource. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Allow-Headers` + response header are separated by a comma (","). + + When the `AllowHeaders` field is configured with one or more headers, the + gateway must return the `Access-Control-Allow-Headers` response header + which value is present in the `AllowHeaders` field. + + If any header name in the `Access-Control-Request-Headers` request header + is not included in the list of header names specified by the response + header `Access-Control-Allow-Headers`, it will present an error on the + client side. + + If any header name in the `Access-Control-Allow-Headers` response header + does not recognize by the client, it will also occur an error on the + client side. + + A wildcard indicates that the requests with all HTTP headers are allowed. + The `Access-Control-Allow-Headers` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowHeaders` field + specified with the `*` wildcard, the gateway must specify one or more + HTTP headers in the value of the `Access-Control-Allow-Headers` response + header. The value of the header `Access-Control-Allow-Headers` is same as + the `Access-Control-Request-Headers` header provided by the client. If + the header `Access-Control-Request-Headers` is not included in the + request, the gateway will omit the `Access-Control-Allow-Headers` + response header, instead of specifying the `*` wildcard. A Gateway + implementation may choose to add implementation-specific default headers. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + allowMethods: + description: |- + AllowMethods indicates which HTTP methods are supported for accessing the + requested resource. + + Valid values are any method defined by RFC9110, along with the special + value `*`, which represents all HTTP methods are allowed. + + Method names are case sensitive, so these values are also case-sensitive. + (See https://www.rfc-editor.org/rfc/rfc2616#section-5.1.1) + + Multiple method names in the value of the `Access-Control-Allow-Methods` + response header are separated by a comma (","). + + A CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`. + (See https://fetch.spec.whatwg.org/#cors-safelisted-method) The + CORS-safelisted methods are always allowed, regardless of whether they + are specified in the `AllowMethods` field. + + When the `AllowMethods` field is configured with one or more methods, the + gateway must return the `Access-Control-Allow-Methods` response header + which value is present in the `AllowMethods` field. + + If the HTTP method of the `Access-Control-Request-Method` request header + is not included in the list of methods specified by the response header + `Access-Control-Allow-Methods`, it will present an error on the client + side. + + The `Access-Control-Allow-Methods` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowMethods` field + specified with the `*` wildcard, the gateway must specify one HTTP method + in the value of the Access-Control-Allow-Methods response header. The + value of the header `Access-Control-Allow-Methods` is same as the + `Access-Control-Request-Method` header provided by the client. If the + header `Access-Control-Request-Method` is not included in the request, + the gateway will omit the `Access-Control-Allow-Methods` response header, + instead of specifying the `*` wildcard. A Gateway implementation may + choose to add implementation-specific default methods. + + Support: Extended + items: + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + - '*' + type: string + maxItems: 9 + type: array + x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowMethods cannot contain '*' alongside + other methods + rule: '!(''*'' in self && self.size() > 1)' + allowOrigins: + description: |- + AllowOrigins indicates whether the response can be shared with requested + resource from the given `Origin`. + + The `Origin` consists of a scheme and a host, with an optional port, and + takes the form `://(:)`. + + Valid values for scheme are: `http` and `https`. + + Valid values for port are any integer between 1 and 65535 (the list of + available TCP/UDP ports). Note that, if not included, port `80` is + assumed for `http` scheme origins, and port `443` is assumed for `https` + origins. This may affect origin matching. + + The host part of the origin may contain the wildcard character `*`. These + wildcard characters behave as follows: + + * `*` is a greedy match to the _left_, including any number of + DNS labels to the left of its position. This also means that + `*` will include any number of period `.` characters to the + left of its position. + * A wildcard by itself matches all hosts. + + An origin value that includes _only_ the `*` character indicates requests + from all `Origin`s are allowed. + + When the `AllowOrigins` field is configured with multiple origins, it + means the server supports clients from multiple origins. If the request + `Origin` matches the configured allowed origins, the gateway must return + the given `Origin` and sets value of the header + `Access-Control-Allow-Origin` same as the `Origin` header provided by the + client. + + The status code of a successful response to a "preflight" request is + always an OK status (i.e., 204 or 200). + + If the request `Origin` does not match the configured allowed origins, + the gateway returns 204/200 response but doesn't set the relevant + cross-origin response headers. Alternatively, the gateway responds with + 403 status to the "preflight" request is denied, coupled with omitting + the CORS headers. The cross-origin request fails on the client side. + Therefore, the client doesn't attempt the actual cross-origin request. + + The `Access-Control-Allow-Origin` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowOrigins` field + specified with the `*` wildcard, the gateway must return a single origin + in the value of the `Access-Control-Allow-Origin` response header, + instead of specifying the `*` wildcard. The value of the header + `Access-Control-Allow-Origin` is same as the `Origin` header provided by + the client. + + Support: Extended + items: + description: |- + The AbsoluteURI MUST NOT be a relative URI, and it MUST follow the URI syntax and + encoding rules specified in RFC3986. The AbsoluteURI MUST include both a + scheme (e.g., "http" or "spiffe") and a scheme-specific-part. URIs that + include an authority MUST include a fully qualified domain name or + IP address as the host. + maxLength: 253 + minLength: 1 + pattern: ^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))? + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + exposeHeaders: + description: |- + ExposeHeaders indicates which HTTP response headers can be exposed + to client-side scripts in response to a cross-origin request. + + A CORS-safelisted response header is an HTTP header in a CORS response + that it is considered safe to expose to the client scripts. + The CORS-safelisted response headers include the following headers: + `Cache-Control` + `Content-Language` + `Content-Length` + `Content-Type` + `Expires` + `Last-Modified` + `Pragma` + (See https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name) + The CORS-safelisted response headers are exposed to client by default. + + When an HTTP header name is specified using the `ExposeHeaders` field, + this additional header will be exposed as part of the response to the + client. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Expose-Headers` + response header are separated by a comma (","). + + A wildcard indicates that the responses with all HTTP headers are exposed + to clients. The `Access-Control-Expose-Headers` response header can only + use `*` wildcard as value when the `AllowCredentials` field is + unspecified. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + maxAge: + default: 5 + description: |- + MaxAge indicates the duration (in seconds) for the client to cache the + results of a "preflight" request. + + The information provided by the `Access-Control-Allow-Methods` and + `Access-Control-Allow-Headers` response headers can be cached by the + client until the time specified by `Access-Control-Max-Age` elapses. + + The default value of `Access-Control-Max-Age` response header is 5 + (seconds). + format: int32 + minimum: 1 + type: integer + type: object + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + This filter can be used multiple times within the same rule. + + Support: Implementation-specific + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal + to denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be + specified in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + requestRedirect: + description: |- + RequestRedirect defines a schema for a filter that responds to the + request with an HTTP redirection. + + Support: Core + properties: + hostname: + description: |- + Hostname is the hostname to be used in the value of the `Location` + header in the response. + When empty, the hostname in the `Host` header of the request is used. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines parameters used to modify the path of the incoming request. + The modified path is then used to construct the `Location` header. When + empty, the request path is used as-is. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified + when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? + has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when + replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type + == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified + when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' + ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' + when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + port: + description: |- + Port is the port to be used in the value of the `Location` + header in the response. + + If no port is specified, the redirect port MUST be derived using the + following rules: + + * If redirect scheme is not-empty, the redirect port MUST be the well-known + port associated with the redirect scheme. Specifically "http" to port 80 + and "https" to port 443. If the redirect scheme does not have a + well-known port, the listener port of the Gateway SHOULD be used. + * If redirect scheme is empty, the redirect port MUST be the Gateway + Listener port. + + Implementations SHOULD NOT add the port number in the 'Location' + header in the following cases: + + * A Location header that will use HTTP (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 80. + * A Location header that will use HTTPS (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 443. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: |- + Scheme is the scheme to be used in the value of the `Location` header in + the response. When empty, the scheme of the request is used. + + Scheme redirects can affect the port of the redirect, for more information, + refer to the documentation for the port field of this filter. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Extended + enum: + - http + - https + type: string + statusCode: + default: 302 + description: |- + StatusCode is the HTTP status code to be used in response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Core + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations must support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by + specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` should be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + - CORS + type: string + urlRewrite: + description: |- + URLRewrite defines a schema for a filter that modifies a request during forwarding. + + Support: Extended + properties: + hostname: + description: |- + Hostname is the value to be used to replace the Host header value during + forwarding. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines a path rewrite. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified + when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? + has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when + replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type + == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified + when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' + ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' + when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil + if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type + != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type + == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil + if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type + != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for + RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == + ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the + filter.type is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != + ''RequestRedirect'')' + - message: filter.requestRedirect must be specified + for RequestRedirect filter.type + rule: '!(!has(self.requestRedirect) && self.type == + ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type + is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite + filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for + ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + - message: filter.cors must be nil if the filter.type + is not CORS + rule: '!(has(self.cors) && self.type != ''CORS'')' + - message: filter.cors must be specified for CORS filter.type + rule: '!(!has(self.cors) && self.type == ''CORS'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') + && self.exists(f, f.type == ''URLRewrite''))' + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') + && self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() + <= 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() + <= 1 + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + filters: + description: |- + Filters define the filters that are applied to requests that match + this rule. + + Wherever possible, implementations SHOULD implement filters in the order + they are specified. + + Implementations MAY choose to implement this ordering strictly, rejecting + any combination or order of filters that cannot be supported. If implementations + choose a strict interpretation of filter ordering, they MUST clearly document + that behavior. + + To reject an invalid combination or order of filters, implementations SHOULD + consider the Route Rules with this configuration invalid. If all Route Rules + in a Route are invalid, the entire Route would be considered invalid. If only + a portion of Route Rules are invalid, implementations MUST set the + "PartiallyInvalid" condition for the Route. + + Conformance-levels at this level are defined based on the type of filter: + + - ALL core filters MUST be supported by all implementations. + - Implementers are encouraged to support extended filters. + - Implementation-specific custom filters have no API guarantees across + implementations. + + Specifying the same filter multiple times is not supported unless explicitly + indicated in the filter. + + All filters are expected to be compatible with each other except for the + URLRewrite and RequestRedirect filters, which may not be combined. If an + implementation cannot support other combinations of filters, they must clearly + document that limitation. In cases where incompatible or unsupported + filters are specified and cause the `Accepted` condition to be set to status + `False`, implementations may use the `IncompatibleFilters` reason to specify + this configuration error. + + Support: Core + items: + description: |- + HTTPRouteFilter defines processing steps that must be completed during the + request or response lifecycle. HTTPRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + cors: + description: |- + CORS defines a schema for a filter that responds to the + cross-origin request based on HTTP response header. + + Support: Extended + properties: + allowCredentials: + description: |- + AllowCredentials indicates whether the actual cross-origin request allows + to include credentials. + + The only valid value for the `Access-Control-Allow-Credentials` response + header is true (case-sensitive). + + If the credentials are not allowed in cross-origin requests, the gateway + will omit the header `Access-Control-Allow-Credentials` entirely rather + than setting its value to false. + + Support: Extended + enum: + - true + type: boolean + allowHeaders: + description: |- + AllowHeaders indicates which HTTP request headers are supported for + accessing the requested resource. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Allow-Headers` + response header are separated by a comma (","). + + When the `AllowHeaders` field is configured with one or more headers, the + gateway must return the `Access-Control-Allow-Headers` response header + which value is present in the `AllowHeaders` field. + + If any header name in the `Access-Control-Request-Headers` request header + is not included in the list of header names specified by the response + header `Access-Control-Allow-Headers`, it will present an error on the + client side. + + If any header name in the `Access-Control-Allow-Headers` response header + does not recognize by the client, it will also occur an error on the + client side. + + A wildcard indicates that the requests with all HTTP headers are allowed. + The `Access-Control-Allow-Headers` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowHeaders` field + specified with the `*` wildcard, the gateway must specify one or more + HTTP headers in the value of the `Access-Control-Allow-Headers` response + header. The value of the header `Access-Control-Allow-Headers` is same as + the `Access-Control-Request-Headers` header provided by the client. If + the header `Access-Control-Request-Headers` is not included in the + request, the gateway will omit the `Access-Control-Allow-Headers` + response header, instead of specifying the `*` wildcard. A Gateway + implementation may choose to add implementation-specific default headers. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + allowMethods: + description: |- + AllowMethods indicates which HTTP methods are supported for accessing the + requested resource. + + Valid values are any method defined by RFC9110, along with the special + value `*`, which represents all HTTP methods are allowed. + + Method names are case sensitive, so these values are also case-sensitive. + (See https://www.rfc-editor.org/rfc/rfc2616#section-5.1.1) + + Multiple method names in the value of the `Access-Control-Allow-Methods` + response header are separated by a comma (","). + + A CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`. + (See https://fetch.spec.whatwg.org/#cors-safelisted-method) The + CORS-safelisted methods are always allowed, regardless of whether they + are specified in the `AllowMethods` field. + + When the `AllowMethods` field is configured with one or more methods, the + gateway must return the `Access-Control-Allow-Methods` response header + which value is present in the `AllowMethods` field. + + If the HTTP method of the `Access-Control-Request-Method` request header + is not included in the list of methods specified by the response header + `Access-Control-Allow-Methods`, it will present an error on the client + side. + + The `Access-Control-Allow-Methods` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowMethods` field + specified with the `*` wildcard, the gateway must specify one HTTP method + in the value of the Access-Control-Allow-Methods response header. The + value of the header `Access-Control-Allow-Methods` is same as the + `Access-Control-Request-Method` header provided by the client. If the + header `Access-Control-Request-Method` is not included in the request, + the gateway will omit the `Access-Control-Allow-Methods` response header, + instead of specifying the `*` wildcard. A Gateway implementation may + choose to add implementation-specific default methods. + + Support: Extended + items: + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + - '*' + type: string + maxItems: 9 + type: array + x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowMethods cannot contain '*' alongside + other methods + rule: '!(''*'' in self && self.size() > 1)' + allowOrigins: + description: |- + AllowOrigins indicates whether the response can be shared with requested + resource from the given `Origin`. + + The `Origin` consists of a scheme and a host, with an optional port, and + takes the form `://(:)`. + + Valid values for scheme are: `http` and `https`. + + Valid values for port are any integer between 1 and 65535 (the list of + available TCP/UDP ports). Note that, if not included, port `80` is + assumed for `http` scheme origins, and port `443` is assumed for `https` + origins. This may affect origin matching. + + The host part of the origin may contain the wildcard character `*`. These + wildcard characters behave as follows: + + * `*` is a greedy match to the _left_, including any number of + DNS labels to the left of its position. This also means that + `*` will include any number of period `.` characters to the + left of its position. + * A wildcard by itself matches all hosts. + + An origin value that includes _only_ the `*` character indicates requests + from all `Origin`s are allowed. + + When the `AllowOrigins` field is configured with multiple origins, it + means the server supports clients from multiple origins. If the request + `Origin` matches the configured allowed origins, the gateway must return + the given `Origin` and sets value of the header + `Access-Control-Allow-Origin` same as the `Origin` header provided by the + client. + + The status code of a successful response to a "preflight" request is + always an OK status (i.e., 204 or 200). + + If the request `Origin` does not match the configured allowed origins, + the gateway returns 204/200 response but doesn't set the relevant + cross-origin response headers. Alternatively, the gateway responds with + 403 status to the "preflight" request is denied, coupled with omitting + the CORS headers. The cross-origin request fails on the client side. + Therefore, the client doesn't attempt the actual cross-origin request. + + The `Access-Control-Allow-Origin` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowOrigins` field + specified with the `*` wildcard, the gateway must return a single origin + in the value of the `Access-Control-Allow-Origin` response header, + instead of specifying the `*` wildcard. The value of the header + `Access-Control-Allow-Origin` is same as the `Origin` header provided by + the client. + + Support: Extended + items: + description: |- + The AbsoluteURI MUST NOT be a relative URI, and it MUST follow the URI syntax and + encoding rules specified in RFC3986. The AbsoluteURI MUST include both a + scheme (e.g., "http" or "spiffe") and a scheme-specific-part. URIs that + include an authority MUST include a fully qualified domain name or + IP address as the host. + maxLength: 253 + minLength: 1 + pattern: ^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))? + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + exposeHeaders: + description: |- + ExposeHeaders indicates which HTTP response headers can be exposed + to client-side scripts in response to a cross-origin request. + + A CORS-safelisted response header is an HTTP header in a CORS response + that it is considered safe to expose to the client scripts. + The CORS-safelisted response headers include the following headers: + `Cache-Control` + `Content-Language` + `Content-Length` + `Content-Type` + `Expires` + `Last-Modified` + `Pragma` + (See https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name) + The CORS-safelisted response headers are exposed to client by default. + + When an HTTP header name is specified using the `ExposeHeaders` field, + this additional header will be exposed as part of the response to the + client. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Expose-Headers` + response header are separated by a comma (","). + + A wildcard indicates that the responses with all HTTP headers are exposed + to clients. The `Access-Control-Expose-Headers` response header can only + use `*` wildcard as value when the `AllowCredentials` field is + unspecified. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + maxAge: + default: 5 + description: |- + MaxAge indicates the duration (in seconds) for the client to cache the + results of a "preflight" request. + + The information provided by the `Access-Control-Allow-Methods` and + `Access-Control-Allow-Headers` response headers can be cached by the + client until the time specified by `Access-Control-Max-Age` elapses. + + The default value of `Access-Control-Max-Age` response header is 5 + (seconds). + format: int32 + minimum: 1 + type: integer + type: object + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + This filter can be used multiple times within the same rule. + + Support: Implementation-specific + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal to + denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be specified + in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + requestRedirect: + description: |- + RequestRedirect defines a schema for a filter that responds to the + request with an HTTP redirection. + + Support: Core + properties: + hostname: + description: |- + Hostname is the hostname to be used in the value of the `Location` + header in the response. + When empty, the hostname in the `Host` header of the request is used. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines parameters used to modify the path of the incoming request. + The modified path is then used to construct the `Location` header. When + empty, the request path is used as-is. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when + type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) + : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath + is set + rule: 'has(self.replaceFullPath) ? self.type == + ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when + type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) + : true' + - message: type must be 'ReplacePrefixMatch' when + replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + port: + description: |- + Port is the port to be used in the value of the `Location` + header in the response. + + If no port is specified, the redirect port MUST be derived using the + following rules: + + * If redirect scheme is not-empty, the redirect port MUST be the well-known + port associated with the redirect scheme. Specifically "http" to port 80 + and "https" to port 443. If the redirect scheme does not have a + well-known port, the listener port of the Gateway SHOULD be used. + * If redirect scheme is empty, the redirect port MUST be the Gateway + Listener port. + + Implementations SHOULD NOT add the port number in the 'Location' + header in the following cases: + + * A Location header that will use HTTP (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 80. + * A Location header that will use HTTPS (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 443. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: |- + Scheme is the scheme to be used in the value of the `Location` header in + the response. When empty, the scheme of the request is used. + + Scheme redirects can affect the port of the redirect, for more information, + refer to the documentation for the port field of this filter. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Extended + enum: + - http + - https + type: string + statusCode: + default: 302 + description: |- + StatusCode is the HTTP status code to be used in response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Core + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations must support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by + specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` should be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + - CORS + type: string + urlRewrite: + description: |- + URLRewrite defines a schema for a filter that modifies a request during forwarding. + + Support: Extended + properties: + hostname: + description: |- + Hostname is the value to be used to replace the Host header value during + forwarding. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines a path rewrite. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when + type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) + : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath + is set + rule: 'has(self.replaceFullPath) ? self.type == + ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when + type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) + : true' + - message: type must be 'ReplacePrefixMatch' when + replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the + filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != + ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == + ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the + filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != + ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror + filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the filter.type + is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')' + - message: filter.requestRedirect must be specified for RequestRedirect + filter.type + rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type + is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite + filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef + filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + - message: filter.cors must be nil if the filter.type is not + CORS + rule: '!(has(self.cors) && self.type != ''CORS'')' + - message: filter.cors must be specified for CORS filter.type + rule: '!(!has(self.cors) && self.type == ''CORS'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') && + self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() <= + 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() <= 1 + matches: + default: + - path: + type: PathPrefix + value: / + description: |- + Matches define conditions used for matching the rule against incoming + HTTP requests. Each match is independent, i.e. this rule will be matched + if **any** one of the matches is satisfied. + + For example, take the following matches configuration: + + ``` + matches: + - path: + value: "/foo" + headers: + - name: "version" + value: "v2" + - path: + value: "/v2/foo" + ``` + + For a request to match against this rule, a request must satisfy + EITHER of the two conditions: + + - path prefixed with `/foo` AND contains the header `version: v2` + - path prefix of `/v2/foo` + + See the documentation for HTTPRouteMatch on how to specify multiple + match conditions that should be ANDed together. + + If no matches are specified, the default is a prefix + path match on "/", which has the effect of matching every + HTTP request. + + Proxy or Load Balancer routing configuration generated from HTTPRoutes + MUST prioritize matches based on the following criteria, continuing on + ties. Across all rules specified on applicable Routes, precedence must be + given to the match having: + + * "Exact" path match. + * "Prefix" path match with largest number of characters. + * Method match. + * Largest number of header matches. + * Largest number of query param matches. + + Note: The precedence of RegularExpression path matches are implementation-specific. + + If ties still exist across multiple Routes, matching precedence MUST be + determined in order of the following criteria, continuing on ties: + + * The oldest Route based on creation timestamp. + * The Route appearing first in alphabetical order by + "{namespace}/{name}". + + If ties still exist within an HTTPRoute, matching precedence MUST be granted + to the FIRST matching rule (in list order) with a match meeting the above + criteria. + + When no rules matching a request have been successfully attached to the + parent a request is coming from, a HTTP 404 status code MUST be returned. + items: + description: "HTTPRouteMatch defines the predicate used to + match requests to a given\naction. Multiple match types + are ANDed together, i.e. the match will\nevaluate to true + only if all conditions are satisfied.\n\nFor example, the + match below will match a HTTP request only if its path\nstarts + with `/foo` AND it contains the `version: v1` header:\n\n```\nmatch:\n\n\tpath:\n\t + \ value: \"/foo\"\n\theaders:\n\t- name: \"version\"\n\t + \ value \"v1\"\n\n```" + properties: + headers: + description: |- + Headers specifies HTTP request header matchers. Multiple match values are + ANDed together, meaning, a request must match all the specified headers + to select the route. + items: + description: |- + HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request + headers. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + + When a header is repeated in an HTTP request, it is + implementation-specific behavior as to how this is represented. + Generally, proxies should follow the guidance from the RFC: + https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding + processing a repeated header, with special handling for "Set-Cookie". + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the header. + + Support: Core (Exact) + + Support: Implementation-specific (RegularExpression) + + Since RegularExpression HeaderMatchType has implementation-specific + conformance, implementations can support POSIX, PCRE or any other dialects + of regular expressions. Please read the implementation's documentation to + determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP Header to + be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: |- + Method specifies HTTP method matcher. + When specified, this route will be matched only if the request has the + specified method. + + Support: Extended + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: |- + Path specifies a HTTP request path matcher. If this field is not + specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: |- + Type specifies how to match against the path Value. + + Support: Core (Exact, PathPrefix) + + Support: Implementation-specific (RegularExpression) + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path to match against. + maxLength: 1024 + type: string + type: object + x-kubernetes-validations: + - message: value must be an absolute path and start with + '/' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.startsWith(''/'') + : true' + - message: must not contain '//' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''//'') + : true' + - message: must not contain '/./' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/./'') + : true' + - message: must not contain '/../' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/../'') + : true' + - message: must not contain '%2f' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2f'') + : true' + - message: must not contain '%2F' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2F'') + : true' + - message: must not contain '#' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''#'') + : true' + - message: must not end with '/..' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/..'') + : true' + - message: must not end with '/.' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/.'') + : true' + - message: type must be one of ['Exact', 'PathPrefix', + 'RegularExpression'] + rule: self.type in ['Exact','PathPrefix'] || self.type + == 'RegularExpression' + - message: must only contain valid characters (matching + ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$) + for types ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.matches(r"""^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$""") + : true' + queryParams: + description: |- + QueryParams specifies HTTP query parameter matchers. Multiple match + values are ANDed together, meaning, a request must match all the + specified query parameters to select the route. + + Support: Extended + items: + description: |- + HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP + query parameters. + properties: + name: + description: |- + Name is the name of the HTTP query param to be matched. This must be an + exact string match. (See + https://tools.ietf.org/html/rfc7230#section-2.7.3). + + If multiple entries specify equivalent query param names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent query param name MUST be ignored. + + If a query param is repeated in an HTTP request, the behavior is + purposely left undefined, since different data planes have different + capabilities. However, it is *recommended* that implementations should + match against the first value of the param if the data plane supports it, + as this behavior is expected in other load balancing contexts outside of + the Gateway API. + + Users SHOULD NOT route traffic based on repeated query params to guard + themselves against potential differences in the implementations. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the query parameter. + + Support: Extended (Exact) + + Support: Implementation-specific (RegularExpression) + + Since RegularExpression QueryParamMatchType has Implementation-specific + conformance, implementations can support POSIX, PCRE or any other + dialects of regular expressions. Please read the implementation's + documentation to determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP query param + to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 64 + type: array + name: + description: |- + Name is the name of the route rule. This name MUST be unique within a Route if it is set. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + retry: + description: |- + Retry defines the configuration for when to retry an HTTP request. + + Support: Extended + properties: + attempts: + description: |- + Attempts specifies the maximum number of times an individual request + from the gateway to a backend should be retried. + + If the maximum number of retries has been attempted without a successful + response from the backend, the Gateway MUST return an error. + + When this field is unspecified, the number of times to attempt to retry + a backend request is implementation-specific. + + Support: Extended + type: integer + backoff: + description: |- + Backoff specifies the minimum duration a Gateway should wait between + retry attempts and is represented in Gateway API Duration formatting. + + For example, setting the `rules[].retry.backoff` field to the value + `100ms` will cause a backend request to first be retried approximately + 100 milliseconds after timing out or receiving a response code configured + to be retryable. + + An implementation MAY use an exponential or alternative backoff strategy + for subsequent retry attempts, MAY cap the maximum backoff duration to + some amount greater than the specified minimum, and MAY add arbitrary + jitter to stagger requests, as long as unsuccessful backend requests are + not retried before the configured minimum duration. + + If a Request timeout (`rules[].timeouts.request`) is configured on the + route, the entire duration of the initial request and any retry attempts + MUST not exceed the Request timeout duration. If any retry attempts are + still in progress when the Request timeout duration has been reached, + these SHOULD be canceled if possible and the Gateway MUST immediately + return a timeout error. + + If a BackendRequest timeout (`rules[].timeouts.backendRequest`) is + configured on the route, any retry attempts which reach the configured + BackendRequest timeout duration without a response SHOULD be canceled if + possible and the Gateway should wait for at least the specified backoff + duration before attempting to retry the backend request again. + + If a BackendRequest timeout is _not_ configured on the route, retry + attempts MAY time out after an implementation default duration, or MAY + remain pending until a configured Request timeout or implementation + default duration for total request time is reached. + + When this field is unspecified, the time to wait between retry attempts + is implementation-specific. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + codes: + description: |- + Codes defines the HTTP response status codes for which a backend request + should be retried. + + Support: Extended + items: + description: |- + HTTPRouteRetryStatusCode defines an HTTP response status code for + which a backend request should be retried. + + Implementations MUST support the following status codes as retryable: + + * 500 + * 502 + * 503 + * 504 + + Implementations MAY support specifying additional discrete values in the + 500-599 range. + + Implementations MAY support specifying discrete values in the 400-499 range, + which are often inadvisable to retry. + maximum: 599 + minimum: 400 + type: integer + type: array + type: object + sessionPersistence: + description: |- + SessionPersistence defines and configures session persistence + for the route rule. + + Support: Extended + properties: + absoluteTimeout: + description: |- + AbsoluteTimeout defines the absolute timeout of the persistent + session. Once the AbsoluteTimeout duration has elapsed, the + session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + cookieConfig: + description: |- + CookieConfig provides configuration settings that are specific + to cookie-based session persistence. + + Support: Core + properties: + lifetimeType: + default: Session + description: |- + LifetimeType specifies whether the cookie has a permanent or + session-based lifetime. A permanent cookie persists until its + specified expiry time, defined by the Expires or Max-Age cookie + attributes, while a session cookie is deleted when the current + session ends. + + When set to "Permanent", AbsoluteTimeout indicates the + cookie's lifetime via the Expires or Max-Age cookie attributes + and is required. + + When set to "Session", AbsoluteTimeout indicates the + absolute lifetime of the cookie tracked by the gateway and + is optional. + + Defaults to "Session". + + Support: Core for "Session" type + + Support: Extended for "Permanent" type + enum: + - Permanent + - Session + type: string + type: object + idleTimeout: + description: |- + IdleTimeout defines the idle timeout of the persistent session. + Once the session has been idle for more than the specified + IdleTimeout duration, the session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + sessionName: + description: |- + SessionName defines the name of the persistent session token + which may be reflected in the cookie or the header. Users + should avoid reusing session names to prevent unintended + consequences, such as rejection or unpredictable behavior. + + Support: Implementation-specific + maxLength: 128 + type: string + type: + default: Cookie + description: |- + Type defines the type of session persistence such as through + the use a header or cookie. Defaults to cookie based session + persistence. + + Support: Core for "Cookie" type + + Support: Extended for "Header" type + enum: + - Cookie + - Header + type: string + type: object + x-kubernetes-validations: + - message: AbsoluteTimeout must be specified when cookie lifetimeType + is Permanent + rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType) + || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)' + timeouts: + description: |- + Timeouts defines the timeouts that can be configured for an HTTP request. + + Support: Extended + properties: + backendRequest: + description: |- + BackendRequest specifies a timeout for an individual request from the gateway + to a backend. This covers the time from when the request first starts being + sent from the gateway to when the full response has been received from the backend. + + Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout + completely. Implementations that cannot completely disable the timeout MUST + instead interpret the zero duration as the longest possible value to which + the timeout can be set. + + An entire client HTTP transaction with a gateway, covered by the Request timeout, + may result in more than one call from the gateway to the destination backend, + for example, if automatic retries are supported. + + The value of BackendRequest must be a Gateway API Duration string as defined by + GEP-2257. When this field is unspecified, its behavior is implementation-specific; + when specified, the value of BackendRequest must be no more than the value of the + Request timeout (since the Request timeout encompasses the BackendRequest timeout). + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + request: + description: |- + Request specifies the maximum duration for a gateway to respond to an HTTP request. + If the gateway has not been able to respond before this deadline is met, the gateway + MUST return a timeout error. + + For example, setting the `rules.timeouts.request` field to the value `10s` in an + `HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds + to complete. + + Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout + completely. Implementations that cannot completely disable the timeout MUST + instead interpret the zero duration as the longest possible value to which + the timeout can be set. + + This timeout is intended to cover as close to the whole request-response transaction + as possible although an implementation MAY choose to start the timeout after the entire + request stream has been received instead of immediately after the transaction is + initiated by the client. + + The value of Request is a Gateway API Duration string as defined by GEP-2257. When this + field is unspecified, request timeout behavior is implementation-specific. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + x-kubernetes-validations: + - message: backendRequest timeout cannot be longer than request + timeout + rule: '!(has(self.request) && has(self.backendRequest) && + duration(self.request) != duration(''0s'') && duration(self.backendRequest) + > duration(self.request))' + type: object + x-kubernetes-validations: + - message: RequestRedirect filter must not be used together with + backendRefs + rule: '(has(self.backendRefs) && size(self.backendRefs) > 0) ? + (!has(self.filters) || self.filters.all(f, !has(f.requestRedirect))): + true' + - message: When using RequestRedirect filter with path.replacePrefixMatch, + exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.requestRedirect) + && has(f.requestRedirect.path) && f.requestRedirect.path.type + == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) + ? ((size(self.matches) != 1 || !has(self.matches[0].path) || + self.matches[0].path.type != ''PathPrefix'') ? false : true) + : true' + - message: When using URLRewrite filter with path.replacePrefixMatch, + exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.urlRewrite) + && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' + && has(f.urlRewrite.path.replacePrefixMatch))) ? ((size(self.matches) + != 1 || !has(self.matches[0].path) || self.matches[0].path.type + != ''PathPrefix'') ? false : true) : true' + - message: Within backendRefs, when using RequestRedirect filter + with path.replacePrefixMatch, exactly one PathPrefix match must + be specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, + (has(b.filters) && b.filters.exists_one(f, has(f.requestRedirect) + && has(f.requestRedirect.path) && f.requestRedirect.path.type + == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) + )) ? ((size(self.matches) != 1 || !has(self.matches[0].path) + || self.matches[0].path.type != ''PathPrefix'') ? false : true) + : true' + - message: Within backendRefs, When using URLRewrite filter with + path.replacePrefixMatch, exactly one PathPrefix match must be + specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, + (has(b.filters) && b.filters.exists_one(f, has(f.urlRewrite) + && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' + && has(f.urlRewrite.path.replacePrefixMatch))) )) ? ((size(self.matches) + != 1 || !has(self.matches[0].path) || self.matches[0].path.type + != ''PathPrefix'') ? false : true) : true' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: While 16 rules and 64 matches per rule are allowed, the + total number of matches across all rules in a route must be less + than 128 + rule: '(self.size() > 0 ? self[0].matches.size() : 0) + (self.size() + > 1 ? self[1].matches.size() : 0) + (self.size() > 2 ? self[2].matches.size() + : 0) + (self.size() > 3 ? self[3].matches.size() : 0) + (self.size() + > 4 ? self[4].matches.size() : 0) + (self.size() > 5 ? self[5].matches.size() + : 0) + (self.size() > 6 ? self[6].matches.size() : 0) + (self.size() + > 7 ? self[7].matches.size() : 0) + (self.size() > 8 ? self[8].matches.size() + : 0) + (self.size() > 9 ? self[9].matches.size() : 0) + (self.size() + > 10 ? self[10].matches.size() : 0) + (self.size() > 11 ? self[11].matches.size() + : 0) + (self.size() > 12 ? self[12].matches.size() : 0) + (self.size() + > 13 ? self[13].matches.size() : 0) + (self.size() > 14 ? self[14].matches.size() + : 0) + (self.size() > 15 ? self[15].matches.size() : 0) <= 128' + - message: Rule name must be unique within the route + rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) + && l1.name == l2.name)) + type: object + status: + description: Status defines the current state of HTTPRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_referencegrants.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: referencegrants.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: ReferenceGrant + listKind: ReferenceGrantList + plural: referencegrants + shortNames: + - refgrant + singular: referencegrant + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: |- + ReferenceGrant identifies kinds of resources in other namespaces that are + trusted to reference the specified kinds of resources in the same namespace + as the policy. + + Each ReferenceGrant can be used to represent a unique trust relationship. + Additional Reference Grants can be used to add to the set of trusted + sources of inbound references for the namespace they are defined within. + + All cross-namespace references in Gateway API (with the exception of cross-namespace + Gateway-route attachment) require a ReferenceGrant. + + ReferenceGrant is a form of runtime verification allowing users to assert + which cross-namespace object references are permitted. Implementations that + support ReferenceGrant MUST NOT permit cross-namespace references which have + no grant, and MUST respond to the removal of a grant by revoking the access + that the grant allowed. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of ReferenceGrant. + properties: + from: + description: |- + From describes the trusted namespaces and kinds that can reference the + resources described in "To". Each entry in this list MUST be considered + to be an additional place that references can be valid from, or to put + this another way, entries MUST be combined using OR. + + Support: Core + items: + description: ReferenceGrantFrom describes trusted namespaces and + kinds. + properties: + group: + description: |- + Group is the group of the referent. + When empty, the Kubernetes core API group is inferred. + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: |- + Kind is the kind of the referent. Although implementations may support + additional resources, the following types are part of the "Core" + support level for this field. + + When used to permit a SecretObjectReference: + + * Gateway + + When used to permit a BackendObjectReference: + + * GRPCRoute + * HTTPRoute + * TCPRoute + * TLSRoute + * UDPRoute + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + namespace: + description: |- + Namespace is the namespace of the referent. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - namespace + type: object + maxItems: 16 + minItems: 1 + type: array + to: + description: |- + To describes the resources that may be referenced by the resources + described in "From". Each entry in this list MUST be considered to be an + additional place that references can be valid to, or to put this another + way, entries MUST be combined using OR. + + Support: Core + items: + description: |- + ReferenceGrantTo describes what Kinds are allowed as targets of the + references. + properties: + group: + description: |- + Group is the group of the referent. + When empty, the Kubernetes core API group is inferred. + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: |- + Kind is the kind of the referent. Although implementations may support + additional resources, the following types are part of the "Core" + support level for this field: + + * Secret when used to permit a SecretObjectReference + * Service when used to permit a BackendObjectReference + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. When unspecified, this policy + refers to all resources of the specified Group and Kind in the local + namespace. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - from + - to + type: object + type: object + served: true + storage: true + subresources: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_tcproutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: tcproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: TCPRoute + listKind: TCPRouteList + plural: tcproutes + singular: tcproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: |- + TCPRoute provides a way to route TCP requests. When combined with a Gateway + listener, it can be used to forward connections on the port specified by the + listener to a set of backends specified by the TCPRoute. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of TCPRoute. + properties: + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) + rules: + description: Rules are a list of TCP matchers and actions. + items: + description: TCPRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. If unspecified or invalid (refers to a nonexistent resource or a + Service with no endpoints), the underlying implementation MUST actively + reject connection attempts to this backend. Connection rejections must + respect weight; if an invalid backend is requested to have 80% of + connections, then 80% of connections must be rejected instead. + + Support: Core for Kubernetes Service + + Support: Extended for Kubernetes ServiceImport + + Support: Implementation-specific for any other resource + + Support for weight: Extended + items: + description: |- + BackendRef defines how a Route should forward a request to a Kubernetes + resource. + + Note that when a namespace different than the local namespace is specified, a + ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + + When the BackendRef points to a Kubernetes Service, implementations SHOULD + honor the appProtocol field if it is set for the target Service Port. + + Implementations supporting appProtocol SHOULD recognize the Kubernetes + Standard Application Protocols defined in KEP-3726. + + If a Service appProtocol isn't specified, an implementation MAY infer the + backend protocol through its own means. Implementations MAY infer the + protocol from the Route type referring to the backend Service. + + If a Route is not able to send traffic to the backend using the specified + protocol then the backend is considered invalid. Implementations MUST set the + "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. + + + Note that when the BackendTLSPolicy object is enabled by the implementation, + there are some extra rules about validity to consider here. See the fields + where this struct is used for more information about the exact behavior. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + minItems: 1 + type: array + name: + description: |- + Name is the name of the route rule. This name MUST be unique within a Route if it is set. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-validations: + - message: Rule name must be unique within the route + rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) + && l1.name == l2.name)) + required: + - rules + type: object + status: + description: Status defines the current state of TCPRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_tlsroutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: tlsroutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: TLSRoute + listKind: TLSRouteList + plural: tlsroutes + singular: tlsroute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: |- + The TLSRoute resource is similar to TCPRoute, but can be configured + to match against TLS-specific metadata. This allows more flexibility + in matching streams for a given TLS listener. + + If you need to forward traffic to a single target for a TLS listener, you + could choose to use a TCPRoute with a TLS listener. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of TLSRoute. + properties: + hostnames: + description: |- + Hostnames defines a set of SNI names that should match against the + SNI attribute of TLS ClientHello message in TLS handshake. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed in SNI names per RFC 6066. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + If a hostname is specified by both the Listener and TLSRoute, there + must be at least one intersecting hostname for the TLSRoute to be + attached to the Listener. For example: + + * A Listener with `test.example.com` as the hostname matches TLSRoutes + that have either not specified any hostnames, or have specified at + least one of `test.example.com` or `*.example.com`. + * A Listener with `*.example.com` as the hostname matches TLSRoutes + that have either not specified any hostnames or have specified at least + one hostname that matches the Listener hostname. For example, + `test.example.com` and `*.example.com` would both match. On the other + hand, `example.com` and `test.example.net` would not match. + + If both the Listener and TLSRoute have specified hostnames, any + TLSRoute hostnames that do not match the Listener hostname MUST be + ignored. For example, if a Listener specified `*.example.com`, and the + TLSRoute specified `test.example.com` and `test.example.net`, + `test.example.net` must not be considered for a match. + + If both the Listener and TLSRoute have specified hostnames, and none + match with the criteria above, then the TLSRoute is not accepted. The + implementation must raise an 'Accepted' Condition with a status of + `False` in the corresponding RouteParentStatus. + + Support: Core + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) + rules: + description: Rules are a list of TLS matchers and actions. + items: + description: TLSRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. If unspecified or invalid (refers to a nonexistent resource or + a Service with no endpoints), the rule performs no forwarding; if no + filters are specified that would result in a response being sent, the + underlying implementation must actively reject request attempts to this + backend, by rejecting the connection or returning a 500 status code. + Request rejections must respect weight; if an invalid backend is + requested to have 80% of requests, then 80% of requests must be rejected + instead. + + Support: Core for Kubernetes Service + + Support: Extended for Kubernetes ServiceImport + + Support: Implementation-specific for any other resource + + Support for weight: Extended + items: + description: |- + BackendRef defines how a Route should forward a request to a Kubernetes + resource. + + Note that when a namespace different than the local namespace is specified, a + ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + + When the BackendRef points to a Kubernetes Service, implementations SHOULD + honor the appProtocol field if it is set for the target Service Port. + + Implementations supporting appProtocol SHOULD recognize the Kubernetes + Standard Application Protocols defined in KEP-3726. + + If a Service appProtocol isn't specified, an implementation MAY infer the + backend protocol through its own means. Implementations MAY infer the + protocol from the Route type referring to the backend Service. + + If a Route is not able to send traffic to the backend using the specified + protocol then the backend is considered invalid. Implementations MUST set the + "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. + + + Note that when the BackendTLSPolicy object is enabled by the implementation, + there are some extra rules about validity to consider here. See the fields + where this struct is used for more information about the exact behavior. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + minItems: 1 + type: array + name: + description: |- + Name is the name of the route rule. This name MUST be unique within a Route if it is set. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-validations: + - message: Rule name must be unique within the route + rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) + && l1.name == l2.name)) + required: + - rules + type: object + status: + description: Status defines the current state of TLSRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.k8s.io_udproutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: udproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: UDPRoute + listKind: UDPRouteList + plural: udproutes + singular: udproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha2 + schema: + openAPIV3Schema: + description: |- + UDPRoute provides a way to route UDP traffic. When combined with a Gateway + listener, it can be used to forward traffic on the port specified by the + listener to a set of backends specified by the UDPRoute. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of UDPRoute. + properties: + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName or port must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__)) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''') && (!has(p1.port) || p1.port == 0) == (!has(p2.port) + || p2.port == 0)): true))' + - message: sectionName or port must be unique when parentRefs includes + 2 or more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || ( has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)) && (((!has(p1.port) || p1.port == 0) && (!has(p2.port) + || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port + == p2.port)))) + rules: + description: Rules are a list of UDP matchers and actions. + items: + description: UDPRouteRule is the configuration for a given rule. + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. If unspecified or invalid (refers to a nonexistent resource or a + Service with no endpoints), the underlying implementation MUST actively + reject connection attempts to this backend. Packet drops must + respect weight; if an invalid backend is requested to have 80% of + the packets, then 80% of packets must be dropped instead. + + Support: Core for Kubernetes Service + + Support: Extended for Kubernetes ServiceImport + + Support: Implementation-specific for any other resource + + Support for weight: Extended + items: + description: |- + BackendRef defines how a Route should forward a request to a Kubernetes + resource. + + Note that when a namespace different than the local namespace is specified, a + ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + + When the BackendRef points to a Kubernetes Service, implementations SHOULD + honor the appProtocol field if it is set for the target Service Port. + + Implementations supporting appProtocol SHOULD recognize the Kubernetes + Standard Application Protocols defined in KEP-3726. + + If a Service appProtocol isn't specified, an implementation MAY infer the + backend protocol through its own means. Implementations MAY infer the + protocol from the Route type referring to the backend Service. + + If a Route is not able to send traffic to the backend using the specified + protocol then the backend is considered invalid. Implementations MUST set the + "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. + + + Note that when the BackendTLSPolicy object is enabled by the implementation, + there are some extra rules about validity to consider here. See the fields + where this struct is used for more information about the exact behavior. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + minItems: 1 + type: array + name: + description: |- + Name is the name of the route rule. This name MUST be unique within a Route if it is set. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-validations: + - message: Rule name must be unique within the route + rule: self.all(l1, !has(l1.name) || self.exists_one(l2, has(l2.name) + && l1.name == l2.name)) + required: + - rules + type: object + status: + description: Status defines the current state of UDPRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.x-k8s.io_xbackendtrafficpolicies.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + labels: + gateway.networking.k8s.io/policy: Direct + name: xbackendtrafficpolicies.gateway.networking.x-k8s.io +spec: + group: gateway.networking.x-k8s.io + names: + categories: + - gateway-api + kind: XBackendTrafficPolicy + listKind: XBackendTrafficPolicyList + plural: xbackendtrafficpolicies + shortNames: + - xbtrafficpolicy + singular: xbackendtrafficpolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + XBackendTrafficPolicy defines the configuration for how traffic to a + target backend should be handled. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of BackendTrafficPolicy. + properties: + retryConstraint: + description: |- + RetryConstraint defines the configuration for when to allow or prevent + further retries to a target backend, by dynamically calculating a 'retry + budget'. This budget is calculated based on the percentage of incoming + traffic composed of retries over a given time interval. Once the budget + is exceeded, additional retries will be rejected. + + For example, if the retry budget interval is 10 seconds, there have been + 1000 active requests in the past 10 seconds, and the allowed percentage + of requests that can be retried is 20% (the default), then 200 of those + requests may be composed of retries. Active requests will only be + considered for the duration of the interval when calculating the retry + budget. Retrying the same original request multiple times within the + retry budget interval will lead to each retry being counted towards + calculating the budget. + + Configuring a RetryConstraint in BackendTrafficPolicy is compatible with + HTTPRoute Retry settings for each HTTPRouteRule that targets the same + backend. While the HTTPRouteRule Retry stanza can specify whether a + request will be retried, and the number of retry attempts each client + may perform, RetryConstraint helps prevent cascading failures such as + retry storms during periods of consistent failures. + + After the retry budget has been exceeded, additional retries to the + backend MUST return a 503 response to the client. + + Additional configurations for defining a constraint on retries MAY be + defined in the future. + + Support: Extended + properties: + budget: + default: + interval: 10s + percent: 20 + description: Budget holds the details of the retry budget configuration. + properties: + interval: + default: 10s + description: |- + Interval defines the duration in which requests will be considered + for calculating the budget for retries. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + x-kubernetes-validations: + - message: interval can not be greater than one hour or less + than one second + rule: '!(duration(self) < duration(''1s'') || duration(self) + > duration(''1h''))' + percent: + default: 20 + description: |- + Percent defines the maximum percentage of active requests that may + be made up of retries. + + Support: Extended + maximum: 100 + minimum: 0 + type: integer + type: object + minRetryRate: + default: + count: 10 + interval: 1s + description: |- + MinRetryRate defines the minimum rate of retries that will be allowable + over a specified duration of time. + + The effective overall minimum rate of retries targeting the backend + service may be much higher, as there can be any number of clients which + are applying this setting locally. + + This ensures that requests can still be retried during periods of low + traffic, where the budget for retries may be calculated as a very low + value. + + Support: Extended + properties: + count: + description: |- + Count specifies the number of requests per time interval. + + Support: Extended + maximum: 1000000 + minimum: 1 + type: integer + interval: + description: |- + Interval specifies the divisor of the rate of requests, the amount of + time during which the given count of requests occur. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + x-kubernetes-validations: + - message: interval can not be greater than one hour + rule: '!(duration(self) == duration(''0s'') || duration(self) + > duration(''1h''))' + type: object + type: object + sessionPersistence: + description: |- + SessionPersistence defines and configures session persistence + for the backend. + + Support: Extended + properties: + absoluteTimeout: + description: |- + AbsoluteTimeout defines the absolute timeout of the persistent + session. Once the AbsoluteTimeout duration has elapsed, the + session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + cookieConfig: + description: |- + CookieConfig provides configuration settings that are specific + to cookie-based session persistence. + + Support: Core + properties: + lifetimeType: + default: Session + description: |- + LifetimeType specifies whether the cookie has a permanent or + session-based lifetime. A permanent cookie persists until its + specified expiry time, defined by the Expires or Max-Age cookie + attributes, while a session cookie is deleted when the current + session ends. + + When set to "Permanent", AbsoluteTimeout indicates the + cookie's lifetime via the Expires or Max-Age cookie attributes + and is required. + + When set to "Session", AbsoluteTimeout indicates the + absolute lifetime of the cookie tracked by the gateway and + is optional. + + Defaults to "Session". + + Support: Core for "Session" type + + Support: Extended for "Permanent" type + enum: + - Permanent + - Session + type: string + type: object + idleTimeout: + description: |- + IdleTimeout defines the idle timeout of the persistent session. + Once the session has been idle for more than the specified + IdleTimeout duration, the session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + sessionName: + description: |- + SessionName defines the name of the persistent session token + which may be reflected in the cookie or the header. Users + should avoid reusing session names to prevent unintended + consequences, such as rejection or unpredictable behavior. + + Support: Implementation-specific + maxLength: 128 + type: string + type: + default: Cookie + description: |- + Type defines the type of session persistence such as through + the use a header or cookie. Defaults to cookie based session + persistence. + + Support: Core for "Cookie" type + + Support: Extended for "Header" type + enum: + - Cookie + - Header + type: string + type: object + x-kubernetes-validations: + - message: AbsoluteTimeout must be specified when cookie lifetimeType + is Permanent + rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType) + || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)' + targetRefs: + description: |- + TargetRefs identifies API object(s) to apply this policy to. + Currently, Backends (A grouping of like endpoints such as Service, + ServiceImport, or any implementation-specific backendRef) are the only + valid API target references. + + Currently, a TargetRef can not be scoped to a specific port on a + Service. + items: + description: |- + LocalPolicyTargetReference identifies an API object to apply a direct or + inherited policy to. This should be used as part of Policy resources + that can target Gateway API resources. For more information on how this + policy attachment model works, and a sample Policy resource, refer to + the policy attachment documentation for Gateway API. + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - group + - kind + - name + x-kubernetes-list-type: map + required: + - targetRefs + type: object + status: + description: Status defines the current state of BackendTrafficPolicy. + properties: + ancestors: + description: |- + Ancestors is a list of ancestor resources (usually Gateways) that are + associated with the policy, and the status of the policy with respect to + each ancestor. When this policy attaches to a parent, the controller that + manages the parent and the ancestors MUST add an entry to this list when + the controller first sees the policy and SHOULD update the entry as + appropriate when the relevant ancestor is modified. + + Note that choosing the relevant ancestor is left to the Policy designers; + an important part of Policy design is designing the right object level at + which to namespace this status. + + Note also that implementations MUST ONLY populate ancestor status for + the Ancestor resources they are responsible for. Implementations MUST + use the ControllerName field to uniquely identify the entries in this list + that they are responsible for. + + Note that to achieve this, the list of PolicyAncestorStatus structs + MUST be treated as a map with a composite key, made up of the AncestorRef + and ControllerName fields combined. + + A maximum of 16 ancestors will be represented in this list. An empty list + means the Policy is not relevant for any ancestors. + + If this slice is full, implementations MUST NOT add further entries. + Instead they MUST consider the policy unimplementable and signal that + on any related resources such as the ancestor that would be referenced + here. For example, if this list was full on BackendTLSPolicy, no + additional Gateways would be able to reference the Service targeted by + the BackendTLSPolicy. + items: + description: |- + PolicyAncestorStatus describes the status of a route with respect to an + associated Ancestor. + + Ancestors refer to objects that are either the Target of a policy or above it + in terms of object hierarchy. For example, if a policy targets a Service, the + Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and + the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most + useful object to place Policy status on, so we recommend that implementations + SHOULD use Gateway as the PolicyAncestorStatus object unless the designers + have a _very_ good reason otherwise. + + In the context of policy attachment, the Ancestor is used to distinguish which + resource results in a distinct application of this policy. For example, if a policy + targets a Service, it may have a distinct result per attached Gateway. + + Policies targeting the same resource may have different effects depending on the + ancestors of those resources. For example, different Gateways targeting the same + Service may have different capabilities, especially if they have different underlying + implementations. + + For example, in BackendTLSPolicy, the Policy attaches to a Service that is + used as a backend in a HTTPRoute that is itself attached to a Gateway. + In this case, the relevant object for status is the Gateway, and that is the + ancestor object referred to in this status. + + Note that a parent is also an ancestor, so for objects where the parent is the + relevant object for status, this struct SHOULD still be used. + + This struct is intended to be used in a slice that's effectively a map, + with a composite key made up of the AncestorRef and the ControllerName. + properties: + ancestorRef: + description: |- + AncestorRef corresponds with a ParentRef in the spec that this + PolicyAncestorStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: Conditions describes the status of the Policy with + respect to the given Ancestor. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - controllerName + type: object + maxItems: 16 + type: array + required: + - ancestors + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml +# +# config/crd/experimental/gateway.networking.x-k8s.io_xlistenersets.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: xlistenersets.gateway.networking.x-k8s.io +spec: + group: gateway.networking.x-k8s.io + names: + categories: + - gateway-api + kind: XListenerSet + listKind: XListenerSetList + plural: xlistenersets + shortNames: + - lset + singular: xlistenerset + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + XListenerSet defines a set of additional listeners + to attach to an existing Gateway. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of ListenerSet. + properties: + listeners: + description: |- + Listeners associated with this ListenerSet. Listeners define + logical endpoints that are bound on this referenced parent Gateway's addresses. + + Listeners in a `Gateway` and their attached `ListenerSets` are concatenated + as a list when programming the underlying infrastructure. Each listener + name does not need to be unique across the Gateway and ListenerSets. + See ListenerEntry.Name for more details. + + Implementations MUST treat the parent Gateway as having the merged + list of all listeners from itself and attached ListenerSets using + the following precedence: + + 1. "parent" Gateway + 2. ListenerSet ordered by creation time (oldest first) + 3. ListenerSet ordered alphabetically by “{namespace}/{name}”. + + An implementation MAY reject listeners by setting the ListenerEntryStatus + `Accepted`` condition to False with the Reason `TooManyListeners` + + If a listener has a conflict, this will be reported in the + Status.ListenerEntryStatus setting the `Conflicted` condition to True. + + Implementations SHOULD be cautious about what information from the + parent or siblings are reported to avoid accidentally leaking + sensitive information that the child would not otherwise have access + to. This can include contents of secrets etc. + items: + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: |- + AllowedRoutes defines the types of routes that MAY be attached to a + Listener and the trusted namespaces where those Route resources MAY be + present. + + Although a client request may match multiple route rules, only one rule + may ultimately receive the request. Matching precedence MUST be + determined in order of the following criteria: + + * The most specific match as defined by the Route type. + * The oldest Route based on creation timestamp. For example, a Route with + a creation timestamp of "2020-09-08 01:02:03" is given precedence over + a Route with a creation timestamp of "2020-09-08 01:02:04". + * If everything else is equivalent, the Route appearing first in + alphabetical order (namespace/name) should be given precedence. For + example, foo/bar is given precedence over foo/baz. + + All valid rules within a Route attached to this Listener should be + implemented. Invalid Route rules can be ignored (sometimes that will mean + the full Route). If a Route rule transitions from valid to invalid, + support for that Route rule should be dropped to ensure consistency. For + example, even if a filter specified by a Route rule is invalid, the rest + of the rules within that Route should still be supported. + properties: + kinds: + description: |- + Kinds specifies the groups and kinds of Routes that are allowed to bind + to this Gateway Listener. When unspecified or empty, the kinds of Routes + selected are determined using the Listener protocol. + + A RouteGroupKind MUST correspond to kinds of Routes that are compatible + with the application protocol specified in the Listener's Protocol field. + If an implementation does not support or recognize this resource type, it + MUST set the "ResolvedRefs" condition to False for this Listener with the + "InvalidRouteKinds" reason. + + Support: Core + items: + description: RouteGroupKind indicates the group and kind + of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: |- + Namespaces indicates namespaces from which Routes may be attached to this + Listener. This is restricted to the namespace of this Gateway by default. + + Support: Core + properties: + from: + default: Same + description: |- + From indicates where Routes will be selected for this Gateway. Possible + values are: + + * All: Routes in all namespaces may be used by this Gateway. + * Selector: Routes in namespaces selected by the selector may be used by + this Gateway. + * Same: Only Routes in the same namespace may be used by this Gateway. + + Support: Core + enum: + - All + - Selector + - Same + type: string + selector: + description: |- + Selector must be specified when From is set to "Selector". In that case, + only Routes in Namespaces matching this Selector will be selected by this + Gateway. This field is ignored for other values of "From". + + Support: Core + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + type: object + type: object + hostname: + description: |- + Hostname specifies the virtual hostname to match for protocol types that + define this concept. When unspecified, all hostnames are matched. This + field is ignored for protocols that don't require hostname based + matching. + + Implementations MUST apply Hostname matching appropriately for each of + the following protocols: + + * TLS: The Listener Hostname MUST match the SNI. + * HTTP: The Listener Hostname MUST match the Host header of the request. + * HTTPS: The Listener Hostname SHOULD match at both the TLS and HTTP + protocol layers as described above. If an implementation does not + ensure that both the SNI and Host header match the Listener hostname, + it MUST clearly document that. + + For HTTPRoute and TLSRoute resources, there is an interaction with the + `spec.hostnames` array. When both listener and route specify hostnames, + there MUST be an intersection between the values for a Route to be + accepted. For more information, refer to the Route specific Hostnames + documentation. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + name: + description: |- + Name is the name of the Listener. This name MUST be unique within a + ListenerSet. + + Name is not required to be unique across a Gateway and ListenerSets. + Routes can attach to a Listener by having a ListenerSet as a parentRef + and setting the SectionName + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: |- + Port is the network port. Multiple listeners may use the + same port, subject to the Listener compatibility rules. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: Protocol specifies the network protocol this listener + expects to receive. + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: |- + TLS is the TLS configuration for the Listener. This field is required if + the Protocol field is "HTTPS" or "TLS". It is invalid to set this field + if the Protocol field is "HTTP", "TCP", or "UDP". + + The association of SNIs to Certificate defined in GatewayTLSConfig is + defined based on the Hostname field for this listener. + + The GatewayClass MUST use the longest matching SNI out of all + available certificates for any TLS handshake. + properties: + certificateRefs: + description: |- + CertificateRefs contains a series of references to Kubernetes objects that + contains TLS certificates and private keys. These certificates are used to + establish a TLS handshake for requests that match the hostname of the + associated listener. + + A single CertificateRef to a Kubernetes Secret has "Core" support. + Implementations MAY choose to support attaching multiple certificates to + a Listener, but this behavior is implementation-specific. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + + This field is required to have at least one element when the mode is set + to "Terminate" (default) and is optional otherwise. + + CertificateRefs can reference to standard Kubernetes resources, i.e. + Secret, or implementation-specific custom resources. + + Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls + + Support: Implementation-specific (More than one reference or other resource types) + items: + description: |- + SecretObjectReference identifies an API object including its namespace, + defaulting to Secret. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example + "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + frontendValidation: + description: |- + FrontendValidation holds configuration information for validating the frontend (client). + Setting this field will require clients to send a client certificate + required for validation during the TLS handshake. In browsers this may result in a dialog appearing + that requests a user to specify the client certificate. + The maximum depth of a certificate chain accepted in verification is Implementation specific. + + Support: Extended + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to + Kubernetes objects that contain TLS certificates of + the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the client. + + A single CA certificate reference to a Kubernetes ConfigMap + has "Core" support. + Implementations MAY choose to support attaching multiple CA certificates to + a Listener, but this behavior is implementation-specific. + + Support: Core - A single reference to a Kubernetes ConfigMap + with the CA certificate in a key named `ca.crt`. + + Support: Implementation-specific (More than one reference, or other kinds + of resources). + + References to a resource in a different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + items: + description: |- + ObjectReference identifies an API object including its namespace. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When set to the empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "ConfigMap" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + maxItems: 8 + minItems: 1 + type: array + type: object + mode: + default: Terminate + description: |- + Mode defines the TLS behavior for the TLS session initiated by the client. + There are two possible modes: + + - Terminate: The TLS session between the downstream client and the + Gateway is terminated at the Gateway. This mode requires certificates + to be specified in some way, such as populating the certificateRefs + field. + - Passthrough: The TLS session is NOT terminated by the Gateway. This + implies that the Gateway can't decipher the TLS stream except for + the ClientHello message of the TLS protocol. The certificateRefs field + is ignored in this mode. + + Support: Core + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Options are a list of key/value pairs to enable extended TLS + configuration for each implementation. For example, configuring the + minimum TLS version or supported cipher suites. + + A set of common keys MAY be defined by the API in the future. To avoid + any ambiguity, implementation-specific definitions MUST use + domain-prefixed names, such as `example.com/my-custom-option`. + Un-prefixed names are reserved for key names defined by Gateway API. + + Support: Implementation-specific + maxProperties: 16 + type: object + type: object + x-kubernetes-validations: + - message: certificateRefs or options must be specified when + mode is Terminate + rule: 'self.mode == ''Terminate'' ? size(self.certificateRefs) + > 0 || size(self.options) > 0 : true' + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: tls must not be specified for protocols ['HTTP', 'TCP', + 'UDP'] + rule: 'self.all(l, l.protocol in [''HTTP'', ''TCP'', ''UDP''] ? + !has(l.tls) : true)' + - message: tls mode must be Terminate for protocol HTTPS + rule: 'self.all(l, (l.protocol == ''HTTPS'' && has(l.tls)) ? (l.tls.mode + == '''' || l.tls.mode == ''Terminate'') : true)' + - message: hostname must not be specified for protocols ['TCP', 'UDP'] + rule: 'self.all(l, l.protocol in [''TCP'', ''UDP''] ? (!has(l.hostname) + || l.hostname == '''') : true)' + - message: Listener name must be unique within the Gateway + rule: self.all(l1, self.exists_one(l2, l1.name == l2.name)) + - message: Combination of port, protocol and hostname must be unique + for each listener + rule: 'self.all(l1, !has(l1.port) || self.exists_one(l2, has(l2.port) + && l1.port == l2.port && l1.protocol == l2.protocol && (has(l1.hostname) + && has(l2.hostname) ? l1.hostname == l2.hostname : !has(l1.hostname) + && !has(l2.hostname))))' + parentRef: + description: ParentRef references the Gateway that the listeners are + attached to. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: Kind is kind of the referent. For example "Gateway". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. If not present, + the namespace of the referent is assumed to be the same as + the namespace of the referring object. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + required: + - listeners + - parentRef + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: Status defines the current state of ListenerSet. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: |- + Conditions describe the current conditions of the ListenerSet. + + Implementations MUST express ListenerSet conditions using the + `ListenerSetConditionType` and `ListenerSetConditionReason` + constants so that operators and tools can converge on a common + vocabulary to describe ListenerSet state. + + Known condition types are: + + * "Accepted" + * "Programmed" + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port + defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: |- + AttachedRoutes represents the total number of Routes that have been + successfully attached to this Listener. + + Successful attachment of a Route to a Listener is based solely on the + combination of the AllowedRoutes field on the corresponding Listener + and the Route's ParentRefs field. A Route is successfully attached to + a Listener when it is selected by the Listener's AllowedRoutes field + AND the Route has a valid ParentRef selecting the whole Gateway + resource or a specific Listener as a parent resource (more detail on + attachment semantics can be found in the documentation on the various + Route kinds ParentRefs fields). Listener or Route status does not impact + successful attachment, i.e. the AttachedRoutes field count MUST be set + for Listeners with condition Accepted: false and MUST count successfully + attached Routes that may themselves have Accepted: false conditions. + + Uses for this field include troubleshooting Route attachment and + measuring blast radius/impact of changes to a Listener. + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this + listener. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status + corresponds to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: Port is the network port the listener is configured + to listen on. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + supportedKinds: + description: |- + SupportedKinds is the list indicating the Kinds supported by this + listener. This MUST represent the kinds an implementation supports for + that Listener configuration. + + If kinds are specified in Spec that are not supported, they MUST NOT + appear in this list and an implementation MUST set the "ResolvedRefs" + condition to "False" with the "InvalidRouteKinds" reason. If both valid + and invalid Route kinds are specified, the implementation MUST + reference the valid Route kinds that have been specified. + items: + description: RouteGroupKind indicates the group and kind of + a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - port + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml +# Copyright 2025 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Gateway API Experimental channel install +# diff --git a/test/helm/gateway-crds-helm/gateway-api-standard-crds.in.yaml b/test/helm/gateway-crds-helm/gateway-api-standard-crds.in.yaml new file mode 100644 index 0000000000..3f5461e219 --- /dev/null +++ b/test/helm/gateway-crds-helm/gateway-api-standard-crds.in.yaml @@ -0,0 +1,6 @@ +crds: + gatewayAPI: + enabled: true + channel: standard + envoyGateway: + enabled: false diff --git a/test/helm/gateway-crds-helm/gateway-api-standard-crds.out.yaml b/test/helm/gateway-crds-helm/gateway-api-standard-crds.out.yaml new file mode 100644 index 0000000000..550f26c4c8 --- /dev/null +++ b/test/helm/gateway-crds-helm/gateway-api-standard-crds.out.yaml @@ -0,0 +1,10618 @@ +--- +# Source: gateway-crds-helm/templates/standard-gatewayapi-crds.yaml +# +# config/crd/standard/gateway.networking.k8s.io_gatewayclasses.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: standard + creationTimestamp: null + name: gatewayclasses.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: GatewayClass + listKind: GatewayClassList + plural: gatewayclasses + shortNames: + - gc + singular: gatewayclass + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .spec.controllerName + name: Controller + type: string + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.description + name: Description + priority: 1 + type: string + name: v1 + schema: + openAPIV3Schema: + description: |- + GatewayClass describes a class of Gateways available to the user for creating + Gateway resources. + + 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. This recommendation is intended to + limit the blast radius of changes to GatewayClass or associated parameters. + If implementations choose to propagate GatewayClass changes to existing + Gateways, that MUST be clearly documented by the implementation. + + Whenever one or more Gateways are using a GatewayClass, implementations SHOULD + add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the + associated GatewayClass. This ensures that a GatewayClass associated with a + Gateway is not deleted while in use. + + GatewayClass is a Cluster level resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClass. + properties: + controllerName: + description: |- + ControllerName is the name of the controller that is managing Gateways of + this class. The value of this field MUST be a domain prefixed path. + + Example: "example.net/gateway-controller". + + This field is not mutable and cannot be empty. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + description: + description: Description helps describe a GatewayClass with more details. + maxLength: 64 + type: string + parametersRef: + description: |- + ParametersRef is a reference to a resource that contains the configuration + parameters corresponding to the GatewayClass. This is optional if the + controller does not require any additional configuration. + + ParametersRef can reference a standard Kubernetes resource, i.e. ConfigMap, + or an implementation-specific custom resource. The resource can be + cluster-scoped or namespace-scoped. + + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the GatewayClass SHOULD be + rejected with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + + A Gateway for this GatewayClass may provide its own `parametersRef`. When both are specified, + the merging behavior is implementation specific. + It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway. + + Support: Implementation-specific + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. + This field is required when referring to a Namespace-scoped resource and + MUST be unset when referring to a Cluster-scoped resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + required: + - controllerName + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: |- + Status defines the current state of GatewayClass. + + Implementations MUST populate status on all GatewayClass resources which + specify their controller name. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: |- + Conditions is the current status from the controller for + this GatewayClass. + + Controllers should prefer to publish conditions using values + of GatewayClassConditionType for the type of each Condition. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.controllerName + name: Controller + type: string + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .spec.description + name: Description + priority: 1 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: |- + GatewayClass describes a class of Gateways available to the user for creating + Gateway resources. + + 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. This recommendation is intended to + limit the blast radius of changes to GatewayClass or associated parameters. + If implementations choose to propagate GatewayClass changes to existing + Gateways, that MUST be clearly documented by the implementation. + + Whenever one or more Gateways are using a GatewayClass, implementations SHOULD + add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the + associated GatewayClass. This ensures that a GatewayClass associated with a + Gateway is not deleted while in use. + + GatewayClass is a Cluster level resource. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GatewayClass. + properties: + controllerName: + description: |- + ControllerName is the name of the controller that is managing Gateways of + this class. The value of this field MUST be a domain prefixed path. + + Example: "example.net/gateway-controller". + + This field is not mutable and cannot be empty. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf + description: + description: Description helps describe a GatewayClass with more details. + maxLength: 64 + type: string + parametersRef: + description: |- + ParametersRef is a reference to a resource that contains the configuration + parameters corresponding to the GatewayClass. This is optional if the + controller does not require any additional configuration. + + ParametersRef can reference a standard Kubernetes resource, i.e. ConfigMap, + or an implementation-specific custom resource. The resource can be + cluster-scoped or namespace-scoped. + + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the GatewayClass SHOULD be + rejected with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + + A Gateway for this GatewayClass may provide its own `parametersRef`. When both are specified, + the merging behavior is implementation specific. + It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway. + + Support: Implementation-specific + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. + This field is required when referring to a Namespace-scoped resource and + MUST be unset when referring to a Cluster-scoped resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + required: + - controllerName + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: |- + Status defines the current state of GatewayClass. + + Implementations MUST populate status on all GatewayClass resources which + specify their controller name. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + description: |- + Conditions is the current status from the controller for + this GatewayClass. + + Controllers should prefer to publish conditions using values + of GatewayClassConditionType for the type of each Condition. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/standard-gatewayapi-crds.yaml +# +# config/crd/standard/gateway.networking.k8s.io_gateways.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: standard + creationTimestamp: null + name: gateways.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: Gateway + listKind: GatewayList + plural: gateways + shortNames: + - gtw + singular: gateway + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.gatewayClassName + name: Class + type: string + - jsonPath: .status.addresses[*].value + name: Address + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + Gateway represents an instance of a service-traffic handling infrastructure + by binding Listeners to a set of IP addresses. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of Gateway. + properties: + addresses: + description: |- + Addresses requested for this Gateway. This is optional and behavior can + depend on the implementation. If a value is set in the spec and the + requested address is invalid or unavailable, the implementation MUST + indicate this in the associated entry in GatewayStatus.Addresses. + + The Addresses field represents a request for the address(es) on the + "outside of the Gateway", that traffic bound for this Gateway will use. + This could be the IP address or hostname of an external load balancer or + other networking infrastructure, or some other address that traffic will + be sent to. + + If no Addresses are specified, the implementation MAY schedule the + Gateway in an implementation-specific manner, assigning an appropriate + set of Addresses. + + The implementation MUST bind all Listeners to every GatewayAddress that + it assigns to the Gateway and add a corresponding entry in + GatewayStatus.Addresses. + + Support: Extended + items: + description: GatewaySpecAddress describes an address that can be + bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: |- + When a value is unspecified, an implementation SHOULD automatically + assign an address matching the requested type if possible. + + If an implementation does not support an empty value, they MUST set the + "Programmed" condition in status to False with a reason of "AddressNotAssigned". + + Examples: `1.2.3.4`, `128::1`, `my-ip-address`. + maxLength: 253 + type: string + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching + ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + true' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: IPAddress values must be unique + rule: 'self.all(a1, a1.type == ''IPAddress'' ? self.exists_one(a2, + a2.type == a1.type && a2.value == a1.value) : true )' + - message: Hostname values must be unique + rule: 'self.all(a1, a1.type == ''Hostname'' ? self.exists_one(a2, + a2.type == a1.type && a2.value == a1.value) : true )' + gatewayClassName: + description: |- + GatewayClassName used for this Gateway. This is the name of a + GatewayClass resource. + maxLength: 253 + minLength: 1 + type: string + infrastructure: + description: |- + Infrastructure defines infrastructure level attributes about this Gateway instance. + + Support: Extended + properties: + annotations: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Annotations that SHOULD be applied to any resources created in response to this Gateway. + + For implementations creating other Kubernetes objects, this should be the `metadata.annotations` field on resources. + For other implementations, this refers to any relevant (implementation specific) "annotations" concepts. + + An implementation may chose to add additional implementation-specific annotations as they see fit. + + Support: Extended + maxProperties: 8 + type: object + x-kubernetes-validations: + - message: Annotation keys must be in the form of an optional + DNS subdomain prefix followed by a required name segment of + up to 63 characters. + rule: self.all(key, key.matches(r"""^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9]$""")) + - message: If specified, the annotation key's prefix must be a + DNS subdomain not longer than 253 characters in total. + rule: self.all(key, key.split("/")[0].size() < 253) + labels: + additionalProperties: + description: |- + LabelValue is the value of a label in the Gateway API. This is used for validation + of maps such as Gateway infrastructure labels. This matches the Kubernetes + label validation rules: + * must be 63 characters or less (can be empty), + * unless empty, must begin and end with an alphanumeric character ([a-z0-9A-Z]), + * could contain dashes (-), underscores (_), dots (.), and alphanumerics between. + + Valid values include: + + * MyValue + * my.name + * 123-my-value + maxLength: 63 + minLength: 0 + pattern: ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$ + type: string + description: |- + Labels that SHOULD be applied to any resources created in response to this Gateway. + + For implementations creating other Kubernetes objects, this should be the `metadata.labels` field on resources. + For other implementations, this refers to any relevant (implementation specific) "labels" concepts. + + An implementation may chose to add additional implementation-specific labels as they see fit. + + If an implementation maps these labels to Pods, or any other resource that would need to be recreated when labels + change, it SHOULD clearly warn about this behavior in documentation. + + Support: Extended + maxProperties: 8 + type: object + x-kubernetes-validations: + - message: Label keys must be in the form of an optional DNS subdomain + prefix followed by a required name segment of up to 63 characters. + rule: self.all(key, key.matches(r"""^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9]$""")) + - message: If specified, the label key's prefix must be a DNS + subdomain not longer than 253 characters in total. + rule: self.all(key, key.split("/")[0].size() < 253) + parametersRef: + description: |- + ParametersRef is a reference to a resource that contains the configuration + parameters corresponding to the Gateway. This is optional if the + controller does not require any additional configuration. + + This follows the same semantics as GatewayClass's `parametersRef`, but on a per-Gateway basis + + The Gateway's GatewayClass may provide its own `parametersRef`. When both are specified, + the merging behavior is implementation specific. + It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway. + + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the Gateway SHOULD be + rejected with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + + Support: Implementation-specific + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + type: object + listeners: + description: |- + Listeners associated with this Gateway. Listeners define + logical endpoints that are bound on this Gateway's addresses. + At least one Listener MUST be specified. + + ## Distinct Listeners + + Each Listener in a set of Listeners (for example, in a single Gateway) + MUST be _distinct_, in that a traffic flow MUST be able to be assigned to + exactly one listener. (This section uses "set of Listeners" rather than + "Listeners in a single Gateway" because implementations MAY merge configuration + from multiple Gateways onto a single data plane, and these rules _also_ + apply in that case). + + Practically, this means that each listener in a set MUST have a unique + combination of Port, Protocol, and, if supported by the protocol, Hostname. + + Some combinations of port, protocol, and TLS settings are considered + Core support and MUST be supported by implementations based on the objects + they support: + + HTTPRoute + + 1. HTTPRoute, Port: 80, Protocol: HTTP + 2. HTTPRoute, Port: 443, Protocol: HTTPS, TLS Mode: Terminate, TLS keypair provided + + TLSRoute + + 1. TLSRoute, Port: 443, Protocol: TLS, TLS Mode: Passthrough + + "Distinct" Listeners have the following property: + + **The implementation can match inbound requests to a single distinct + Listener**. + + When multiple Listeners share values for fields (for + example, two Listeners with the same Port value), the implementation + can match requests to only one of the Listeners using other + Listener fields. + + When multiple listeners have the same value for the Protocol field, then + each of the Listeners with matching Protocol values MUST have different + values for other fields. + + The set of fields that MUST be different for a Listener differs per protocol. + The following rules define the rules for what fields MUST be considered for + Listeners to be distinct with each protocol currently defined in the + Gateway API spec. + + The set of listeners that all share a protocol value MUST have _different_ + values for _at least one_ of these fields to be distinct: + + * **HTTP, HTTPS, TLS**: Port, Hostname + * **TCP, UDP**: Port + + One **very** important rule to call out involves what happens when an + implementation: + + * Supports TCP protocol Listeners, as well as HTTP, HTTPS, or TLS protocol + Listeners, and + * sees HTTP, HTTPS, or TLS protocols with the same `port` as one with TCP + Protocol. + + In this case all the Listeners that share a port with the + TCP Listener are not distinct and so MUST NOT be accepted. + + If an implementation does not support TCP Protocol Listeners, then the + previous rule does not apply, and the TCP Listeners SHOULD NOT be + accepted. + + Note that the `tls` field is not used for determining if a listener is distinct, because + Listeners that _only_ differ on TLS config will still conflict in all cases. + + ### Listeners that are distinct only by Hostname + + When the Listeners are distinct based only on Hostname, inbound request + hostnames MUST match from the most specific to least specific Hostname + values to choose the correct Listener and its associated set of Routes. + + Exact matches MUST be processed before wildcard matches, and wildcard + matches MUST be processed before fallback (empty Hostname value) + matches. For example, `"foo.example.com"` takes precedence over + `"*.example.com"`, and `"*.example.com"` takes precedence over `""`. + + Additionally, if there are multiple wildcard entries, more specific + wildcard entries must be processed before less specific wildcard entries. + For example, `"*.foo.example.com"` takes precedence over `"*.example.com"`. + + The precise definition here is that the higher the number of dots in the + hostname to the right of the wildcard character, the higher the precedence. + + The wildcard character will match any number of characters _and dots_ to + the left, however, so `"*.example.com"` will match both + `"foo.bar.example.com"` _and_ `"bar.example.com"`. + + ## Handling indistinct Listeners + + If a set of Listeners contains Listeners that are not distinct, then those + Listeners are _Conflicted_, and the implementation MUST set the "Conflicted" + condition in the Listener Status to "True". + + The words "indistinct" and "conflicted" are considered equivalent for the + purpose of this documentation. + + Implementations MAY choose to accept a Gateway with some Conflicted + Listeners only if they only accept the partial Listener set that contains + no Conflicted Listeners. + + Specifically, an implementation MAY accept a partial Listener set subject to + the following rules: + + * The implementation MUST NOT pick one conflicting Listener as the winner. + ALL indistinct Listeners must not be accepted for processing. + * At least one distinct Listener MUST be present, or else the Gateway effectively + contains _no_ Listeners, and must be rejected from processing as a whole. + + The implementation MUST set a "ListenersNotValid" condition on the + Gateway Status when the Gateway contains Conflicted Listeners whether or + not they accept the Gateway. That Condition SHOULD clearly + indicate in the Message which Listeners are conflicted, and which are + Accepted. Additionally, the Listener status for those listeners SHOULD + indicate which Listeners are conflicted and not Accepted. + + ## General Listener behavior + + Note that, for all distinct Listeners, requests SHOULD match at most one Listener. + For example, if Listeners are defined for "foo.example.com" and "*.example.com", a + request to "foo.example.com" SHOULD only be routed using routes attached + to the "foo.example.com" Listener (and not the "*.example.com" Listener). + + This concept is known as "Listener Isolation", and it is an Extended feature + of Gateway API. Implementations that do not support Listener Isolation MUST + clearly document this, and MUST NOT claim support for the + `GatewayHTTPListenerIsolation` feature. + + Implementations that _do_ support Listener Isolation SHOULD claim support + for the Extended `GatewayHTTPListenerIsolation` feature and pass the associated + conformance tests. + + ## Compatible Listeners + + A Gateway's Listeners are considered _compatible_ if: + + 1. They are distinct. + 2. The implementation can serve them in compliance with the Addresses + requirement that all Listeners are available on all assigned + addresses. + + Compatible combinations in Extended support are expected to vary across + implementations. A combination that is compatible for one implementation + may not be compatible for another. + + For example, an implementation that cannot serve both TCP and UDP listeners + on the same address, or cannot mix HTTPS and generic TLS listens on the same port + would not consider those cases compatible, even though they are distinct. + + Implementations MAY merge separate Gateways onto a single set of + Addresses if all Listeners across all Gateways are compatible. + + In a future release the MinItems=1 requirement MAY be dropped. + + Support: Core + items: + description: |- + Listener embodies the concept of a logical endpoint where a Gateway accepts + network connections. + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: |- + AllowedRoutes defines the types of routes that MAY be attached to a + Listener and the trusted namespaces where those Route resources MAY be + present. + + Although a client request may match multiple route rules, only one rule + may ultimately receive the request. Matching precedence MUST be + determined in order of the following criteria: + + * The most specific match as defined by the Route type. + * The oldest Route based on creation timestamp. For example, a Route with + a creation timestamp of "2020-09-08 01:02:03" is given precedence over + a Route with a creation timestamp of "2020-09-08 01:02:04". + * If everything else is equivalent, the Route appearing first in + alphabetical order (namespace/name) should be given precedence. For + example, foo/bar is given precedence over foo/baz. + + All valid rules within a Route attached to this Listener should be + implemented. Invalid Route rules can be ignored (sometimes that will mean + the full Route). If a Route rule transitions from valid to invalid, + support for that Route rule should be dropped to ensure consistency. For + example, even if a filter specified by a Route rule is invalid, the rest + of the rules within that Route should still be supported. + + Support: Core + properties: + kinds: + description: |- + Kinds specifies the groups and kinds of Routes that are allowed to bind + to this Gateway Listener. When unspecified or empty, the kinds of Routes + selected are determined using the Listener protocol. + + A RouteGroupKind MUST correspond to kinds of Routes that are compatible + with the application protocol specified in the Listener's Protocol field. + If an implementation does not support or recognize this resource type, it + MUST set the "ResolvedRefs" condition to False for this Listener with the + "InvalidRouteKinds" reason. + + Support: Core + items: + description: RouteGroupKind indicates the group and kind + of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: |- + Namespaces indicates namespaces from which Routes may be attached to this + Listener. This is restricted to the namespace of this Gateway by default. + + Support: Core + properties: + from: + default: Same + description: |- + From indicates where Routes will be selected for this Gateway. Possible + values are: + + * All: Routes in all namespaces may be used by this Gateway. + * Selector: Routes in namespaces selected by the selector may be used by + this Gateway. + * Same: Only Routes in the same namespace may be used by this Gateway. + + Support: Core + enum: + - All + - Selector + - Same + type: string + selector: + description: |- + Selector must be specified when From is set to "Selector". In that case, + only Routes in Namespaces matching this Selector will be selected by this + Gateway. This field is ignored for other values of "From". + + Support: Core + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + type: object + type: object + hostname: + description: |- + Hostname specifies the virtual hostname to match for protocol types that + define this concept. When unspecified, all hostnames are matched. This + field is ignored for protocols that don't require hostname based + matching. + + Implementations MUST apply Hostname matching appropriately for each of + the following protocols: + + * TLS: The Listener Hostname MUST match the SNI. + * HTTP: The Listener Hostname MUST match the Host header of the request. + * HTTPS: The Listener Hostname SHOULD match both the SNI and Host header. + Note that this does not require the SNI and Host header to be the same. + The semantics of this are described in more detail below. + + To ensure security, Section 11.1 of RFC-6066 emphasizes that server + implementations that rely on SNI hostname matching MUST also verify + hostnames within the application protocol. + + Section 9.1.2 of RFC-7540 provides a mechanism for servers to reject the + reuse of a connection by responding with the HTTP 421 Misdirected Request + status code. This indicates that the origin server has rejected the + request because it appears to have been misdirected. + + To detect misdirected requests, Gateways SHOULD match the authority of + the requests with all the SNI hostname(s) configured across all the + Gateway Listeners on the same port and protocol: + + * If another Listener has an exact match or more specific wildcard entry, + the Gateway SHOULD return a 421. + * If the current Listener (selected by SNI matching during ClientHello) + does not match the Host: + * If another Listener does match the Host the Gateway SHOULD return a + 421. + * If no other Listener matches the Host, the Gateway MUST return a + 404. + + For HTTPRoute and TLSRoute resources, there is an interaction with the + `spec.hostnames` array. When both listener and route specify hostnames, + there MUST be an intersection between the values for a Route to be + accepted. For more information, refer to the Route specific Hostnames + documentation. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + name: + description: |- + Name is the name of the Listener. This name MUST be unique within a + Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: |- + Port is the network port. Multiple listeners may use the + same port, subject to the Listener compatibility rules. + + Support: Core + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: |- + Protocol specifies the network protocol this listener expects to receive. + + Support: Core + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: |- + TLS is the TLS configuration for the Listener. This field is required if + the Protocol field is "HTTPS" or "TLS". It is invalid to set this field + if the Protocol field is "HTTP", "TCP", or "UDP". + + The association of SNIs to Certificate defined in GatewayTLSConfig is + defined based on the Hostname field for this listener. + + The GatewayClass MUST use the longest matching SNI out of all + available certificates for any TLS handshake. + + Support: Core + properties: + certificateRefs: + description: |- + CertificateRefs contains a series of references to Kubernetes objects that + contains TLS certificates and private keys. These certificates are used to + establish a TLS handshake for requests that match the hostname of the + associated listener. + + A single CertificateRef to a Kubernetes Secret has "Core" support. + Implementations MAY choose to support attaching multiple certificates to + a Listener, but this behavior is implementation-specific. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + + This field is required to have at least one element when the mode is set + to "Terminate" (default) and is optional otherwise. + + CertificateRefs can reference to standard Kubernetes resources, i.e. + Secret, or implementation-specific custom resources. + + Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls + + Support: Implementation-specific (More than one reference or other resource types) + items: + description: |- + SecretObjectReference identifies an API object including its namespace, + defaulting to Secret. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example + "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + mode: + default: Terminate + description: |- + Mode defines the TLS behavior for the TLS session initiated by the client. + There are two possible modes: + + - Terminate: The TLS session between the downstream client and the + Gateway is terminated at the Gateway. This mode requires certificates + to be specified in some way, such as populating the certificateRefs + field. + - Passthrough: The TLS session is NOT terminated by the Gateway. This + implies that the Gateway can't decipher the TLS stream except for + the ClientHello message of the TLS protocol. The certificateRefs field + is ignored in this mode. + + Support: Core + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Options are a list of key/value pairs to enable extended TLS + configuration for each implementation. For example, configuring the + minimum TLS version or supported cipher suites. + + A set of common keys MAY be defined by the API in the future. To avoid + any ambiguity, implementation-specific definitions MUST use + domain-prefixed names, such as `example.com/my-custom-option`. + Un-prefixed names are reserved for key names defined by Gateway API. + + Support: Implementation-specific + maxProperties: 16 + type: object + type: object + x-kubernetes-validations: + - message: certificateRefs or options must be specified when + mode is Terminate + rule: 'self.mode == ''Terminate'' ? size(self.certificateRefs) + > 0 || size(self.options) > 0 : true' + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: tls must not be specified for protocols ['HTTP', 'TCP', + 'UDP'] + rule: 'self.all(l, l.protocol in [''HTTP'', ''TCP'', ''UDP''] ? + !has(l.tls) : true)' + - message: tls mode must be Terminate for protocol HTTPS + rule: 'self.all(l, (l.protocol == ''HTTPS'' && has(l.tls)) ? (l.tls.mode + == '''' || l.tls.mode == ''Terminate'') : true)' + - message: hostname must not be specified for protocols ['TCP', 'UDP'] + rule: 'self.all(l, l.protocol in [''TCP'', ''UDP''] ? (!has(l.hostname) + || l.hostname == '''') : true)' + - message: Listener name must be unique within the Gateway + rule: self.all(l1, self.exists_one(l2, l1.name == l2.name)) + - message: Combination of port, protocol and hostname must be unique + for each listener + rule: 'self.all(l1, self.exists_one(l2, l1.port == l2.port && l1.protocol + == l2.protocol && (has(l1.hostname) && has(l2.hostname) ? l1.hostname + == l2.hostname : !has(l1.hostname) && !has(l2.hostname))))' + required: + - gatewayClassName + - listeners + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: Status defines the current state of Gateway. + properties: + addresses: + description: |- + Addresses lists the network addresses that have been bound to the + Gateway. + + This list may differ from the addresses provided in the spec under some + conditions: + + * no addresses are specified, all addresses are dynamically assigned + * a combination of specified and dynamic addresses are assigned + * a specified address was unusable (e.g. already in use) + items: + description: GatewayStatusAddress describes a network address that + is bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: |- + Value of the address. The validity of the values will depend + on the type and support by the controller. + + Examples: `1.2.3.4`, `128::1`, `my-ip-address`. + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching + ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + true' + maxItems: 16 + type: array + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: |- + Conditions describe the current conditions of the Gateway. + + Implementations should prefer to express Gateway conditions + using the `GatewayConditionType` and `GatewayConditionReason` + constants so that operators and tools can converge on a common + vocabulary to describe Gateway state. + + Known condition types are: + + * "Accepted" + * "Programmed" + * "Ready" + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port + defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: |- + AttachedRoutes represents the total number of Routes that have been + successfully attached to this Listener. + + Successful attachment of a Route to a Listener is based solely on the + combination of the AllowedRoutes field on the corresponding Listener + and the Route's ParentRefs field. A Route is successfully attached to + a Listener when it is selected by the Listener's AllowedRoutes field + AND the Route has a valid ParentRef selecting the whole Gateway + resource or a specific Listener as a parent resource (more detail on + attachment semantics can be found in the documentation on the various + Route kinds ParentRefs fields). Listener or Route status does not impact + successful attachment, i.e. the AttachedRoutes field count MUST be set + for Listeners with condition Accepted: false and MUST count successfully + attached Routes that may themselves have Accepted: false conditions. + + Uses for this field include troubleshooting Route attachment and + measuring blast radius/impact of changes to a Listener. + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this + listener. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status + corresponds to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + supportedKinds: + description: |- + SupportedKinds is the list indicating the Kinds supported by this + listener. This MUST represent the kinds an implementation supports for + that Listener configuration. + + If kinds are specified in Spec that are not supported, they MUST NOT + appear in this list and an implementation MUST set the "ResolvedRefs" + condition to "False" with the "InvalidRouteKinds" reason. If both valid + and invalid Route kinds are specified, the implementation MUST + reference the valid Route kinds that have been specified. + items: + description: RouteGroupKind indicates the group and kind of + a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.gatewayClassName + name: Class + type: string + - jsonPath: .status.addresses[*].value + name: Address + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: |- + Gateway represents an instance of a service-traffic handling infrastructure + by binding Listeners to a set of IP addresses. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of Gateway. + properties: + addresses: + description: |- + Addresses requested for this Gateway. This is optional and behavior can + depend on the implementation. If a value is set in the spec and the + requested address is invalid or unavailable, the implementation MUST + indicate this in the associated entry in GatewayStatus.Addresses. + + The Addresses field represents a request for the address(es) on the + "outside of the Gateway", that traffic bound for this Gateway will use. + This could be the IP address or hostname of an external load balancer or + other networking infrastructure, or some other address that traffic will + be sent to. + + If no Addresses are specified, the implementation MAY schedule the + Gateway in an implementation-specific manner, assigning an appropriate + set of Addresses. + + The implementation MUST bind all Listeners to every GatewayAddress that + it assigns to the Gateway and add a corresponding entry in + GatewayStatus.Addresses. + + Support: Extended + items: + description: GatewaySpecAddress describes an address that can be + bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: |- + When a value is unspecified, an implementation SHOULD automatically + assign an address matching the requested type if possible. + + If an implementation does not support an empty value, they MUST set the + "Programmed" condition in status to False with a reason of "AddressNotAssigned". + + Examples: `1.2.3.4`, `128::1`, `my-ip-address`. + maxLength: 253 + type: string + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching + ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + true' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: IPAddress values must be unique + rule: 'self.all(a1, a1.type == ''IPAddress'' ? self.exists_one(a2, + a2.type == a1.type && a2.value == a1.value) : true )' + - message: Hostname values must be unique + rule: 'self.all(a1, a1.type == ''Hostname'' ? self.exists_one(a2, + a2.type == a1.type && a2.value == a1.value) : true )' + gatewayClassName: + description: |- + GatewayClassName used for this Gateway. This is the name of a + GatewayClass resource. + maxLength: 253 + minLength: 1 + type: string + infrastructure: + description: |- + Infrastructure defines infrastructure level attributes about this Gateway instance. + + Support: Extended + properties: + annotations: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Annotations that SHOULD be applied to any resources created in response to this Gateway. + + For implementations creating other Kubernetes objects, this should be the `metadata.annotations` field on resources. + For other implementations, this refers to any relevant (implementation specific) "annotations" concepts. + + An implementation may chose to add additional implementation-specific annotations as they see fit. + + Support: Extended + maxProperties: 8 + type: object + x-kubernetes-validations: + - message: Annotation keys must be in the form of an optional + DNS subdomain prefix followed by a required name segment of + up to 63 characters. + rule: self.all(key, key.matches(r"""^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9]$""")) + - message: If specified, the annotation key's prefix must be a + DNS subdomain not longer than 253 characters in total. + rule: self.all(key, key.split("/")[0].size() < 253) + labels: + additionalProperties: + description: |- + LabelValue is the value of a label in the Gateway API. This is used for validation + of maps such as Gateway infrastructure labels. This matches the Kubernetes + label validation rules: + * must be 63 characters or less (can be empty), + * unless empty, must begin and end with an alphanumeric character ([a-z0-9A-Z]), + * could contain dashes (-), underscores (_), dots (.), and alphanumerics between. + + Valid values include: + + * MyValue + * my.name + * 123-my-value + maxLength: 63 + minLength: 0 + pattern: ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$ + type: string + description: |- + Labels that SHOULD be applied to any resources created in response to this Gateway. + + For implementations creating other Kubernetes objects, this should be the `metadata.labels` field on resources. + For other implementations, this refers to any relevant (implementation specific) "labels" concepts. + + An implementation may chose to add additional implementation-specific labels as they see fit. + + If an implementation maps these labels to Pods, or any other resource that would need to be recreated when labels + change, it SHOULD clearly warn about this behavior in documentation. + + Support: Extended + maxProperties: 8 + type: object + x-kubernetes-validations: + - message: Label keys must be in the form of an optional DNS subdomain + prefix followed by a required name segment of up to 63 characters. + rule: self.all(key, key.matches(r"""^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?([A-Za-z0-9][-A-Za-z0-9_.]{0,61})?[A-Za-z0-9]$""")) + - message: If specified, the label key's prefix must be a DNS + subdomain not longer than 253 characters in total. + rule: self.all(key, key.split("/")[0].size() < 253) + parametersRef: + description: |- + ParametersRef is a reference to a resource that contains the configuration + parameters corresponding to the Gateway. This is optional if the + controller does not require any additional configuration. + + This follows the same semantics as GatewayClass's `parametersRef`, but on a per-Gateway basis + + The Gateway's GatewayClass may provide its own `parametersRef`. When both are specified, + the merging behavior is implementation specific. + It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway. + + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the Gateway SHOULD be + rejected with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + + Support: Implementation-specific + properties: + group: + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + type: object + listeners: + description: |- + Listeners associated with this Gateway. Listeners define + logical endpoints that are bound on this Gateway's addresses. + At least one Listener MUST be specified. + + ## Distinct Listeners + + Each Listener in a set of Listeners (for example, in a single Gateway) + MUST be _distinct_, in that a traffic flow MUST be able to be assigned to + exactly one listener. (This section uses "set of Listeners" rather than + "Listeners in a single Gateway" because implementations MAY merge configuration + from multiple Gateways onto a single data plane, and these rules _also_ + apply in that case). + + Practically, this means that each listener in a set MUST have a unique + combination of Port, Protocol, and, if supported by the protocol, Hostname. + + Some combinations of port, protocol, and TLS settings are considered + Core support and MUST be supported by implementations based on the objects + they support: + + HTTPRoute + + 1. HTTPRoute, Port: 80, Protocol: HTTP + 2. HTTPRoute, Port: 443, Protocol: HTTPS, TLS Mode: Terminate, TLS keypair provided + + TLSRoute + + 1. TLSRoute, Port: 443, Protocol: TLS, TLS Mode: Passthrough + + "Distinct" Listeners have the following property: + + **The implementation can match inbound requests to a single distinct + Listener**. + + When multiple Listeners share values for fields (for + example, two Listeners with the same Port value), the implementation + can match requests to only one of the Listeners using other + Listener fields. + + When multiple listeners have the same value for the Protocol field, then + each of the Listeners with matching Protocol values MUST have different + values for other fields. + + The set of fields that MUST be different for a Listener differs per protocol. + The following rules define the rules for what fields MUST be considered for + Listeners to be distinct with each protocol currently defined in the + Gateway API spec. + + The set of listeners that all share a protocol value MUST have _different_ + values for _at least one_ of these fields to be distinct: + + * **HTTP, HTTPS, TLS**: Port, Hostname + * **TCP, UDP**: Port + + One **very** important rule to call out involves what happens when an + implementation: + + * Supports TCP protocol Listeners, as well as HTTP, HTTPS, or TLS protocol + Listeners, and + * sees HTTP, HTTPS, or TLS protocols with the same `port` as one with TCP + Protocol. + + In this case all the Listeners that share a port with the + TCP Listener are not distinct and so MUST NOT be accepted. + + If an implementation does not support TCP Protocol Listeners, then the + previous rule does not apply, and the TCP Listeners SHOULD NOT be + accepted. + + Note that the `tls` field is not used for determining if a listener is distinct, because + Listeners that _only_ differ on TLS config will still conflict in all cases. + + ### Listeners that are distinct only by Hostname + + When the Listeners are distinct based only on Hostname, inbound request + hostnames MUST match from the most specific to least specific Hostname + values to choose the correct Listener and its associated set of Routes. + + Exact matches MUST be processed before wildcard matches, and wildcard + matches MUST be processed before fallback (empty Hostname value) + matches. For example, `"foo.example.com"` takes precedence over + `"*.example.com"`, and `"*.example.com"` takes precedence over `""`. + + Additionally, if there are multiple wildcard entries, more specific + wildcard entries must be processed before less specific wildcard entries. + For example, `"*.foo.example.com"` takes precedence over `"*.example.com"`. + + The precise definition here is that the higher the number of dots in the + hostname to the right of the wildcard character, the higher the precedence. + + The wildcard character will match any number of characters _and dots_ to + the left, however, so `"*.example.com"` will match both + `"foo.bar.example.com"` _and_ `"bar.example.com"`. + + ## Handling indistinct Listeners + + If a set of Listeners contains Listeners that are not distinct, then those + Listeners are _Conflicted_, and the implementation MUST set the "Conflicted" + condition in the Listener Status to "True". + + The words "indistinct" and "conflicted" are considered equivalent for the + purpose of this documentation. + + Implementations MAY choose to accept a Gateway with some Conflicted + Listeners only if they only accept the partial Listener set that contains + no Conflicted Listeners. + + Specifically, an implementation MAY accept a partial Listener set subject to + the following rules: + + * The implementation MUST NOT pick one conflicting Listener as the winner. + ALL indistinct Listeners must not be accepted for processing. + * At least one distinct Listener MUST be present, or else the Gateway effectively + contains _no_ Listeners, and must be rejected from processing as a whole. + + The implementation MUST set a "ListenersNotValid" condition on the + Gateway Status when the Gateway contains Conflicted Listeners whether or + not they accept the Gateway. That Condition SHOULD clearly + indicate in the Message which Listeners are conflicted, and which are + Accepted. Additionally, the Listener status for those listeners SHOULD + indicate which Listeners are conflicted and not Accepted. + + ## General Listener behavior + + Note that, for all distinct Listeners, requests SHOULD match at most one Listener. + For example, if Listeners are defined for "foo.example.com" and "*.example.com", a + request to "foo.example.com" SHOULD only be routed using routes attached + to the "foo.example.com" Listener (and not the "*.example.com" Listener). + + This concept is known as "Listener Isolation", and it is an Extended feature + of Gateway API. Implementations that do not support Listener Isolation MUST + clearly document this, and MUST NOT claim support for the + `GatewayHTTPListenerIsolation` feature. + + Implementations that _do_ support Listener Isolation SHOULD claim support + for the Extended `GatewayHTTPListenerIsolation` feature and pass the associated + conformance tests. + + ## Compatible Listeners + + A Gateway's Listeners are considered _compatible_ if: + + 1. They are distinct. + 2. The implementation can serve them in compliance with the Addresses + requirement that all Listeners are available on all assigned + addresses. + + Compatible combinations in Extended support are expected to vary across + implementations. A combination that is compatible for one implementation + may not be compatible for another. + + For example, an implementation that cannot serve both TCP and UDP listeners + on the same address, or cannot mix HTTPS and generic TLS listens on the same port + would not consider those cases compatible, even though they are distinct. + + Implementations MAY merge separate Gateways onto a single set of + Addresses if all Listeners across all Gateways are compatible. + + In a future release the MinItems=1 requirement MAY be dropped. + + Support: Core + items: + description: |- + Listener embodies the concept of a logical endpoint where a Gateway accepts + network connections. + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: |- + AllowedRoutes defines the types of routes that MAY be attached to a + Listener and the trusted namespaces where those Route resources MAY be + present. + + Although a client request may match multiple route rules, only one rule + may ultimately receive the request. Matching precedence MUST be + determined in order of the following criteria: + + * The most specific match as defined by the Route type. + * The oldest Route based on creation timestamp. For example, a Route with + a creation timestamp of "2020-09-08 01:02:03" is given precedence over + a Route with a creation timestamp of "2020-09-08 01:02:04". + * If everything else is equivalent, the Route appearing first in + alphabetical order (namespace/name) should be given precedence. For + example, foo/bar is given precedence over foo/baz. + + All valid rules within a Route attached to this Listener should be + implemented. Invalid Route rules can be ignored (sometimes that will mean + the full Route). If a Route rule transitions from valid to invalid, + support for that Route rule should be dropped to ensure consistency. For + example, even if a filter specified by a Route rule is invalid, the rest + of the rules within that Route should still be supported. + + Support: Core + properties: + kinds: + description: |- + Kinds specifies the groups and kinds of Routes that are allowed to bind + to this Gateway Listener. When unspecified or empty, the kinds of Routes + selected are determined using the Listener protocol. + + A RouteGroupKind MUST correspond to kinds of Routes that are compatible + with the application protocol specified in the Listener's Protocol field. + If an implementation does not support or recognize this resource type, it + MUST set the "ResolvedRefs" condition to False for this Listener with the + "InvalidRouteKinds" reason. + + Support: Core + items: + description: RouteGroupKind indicates the group and kind + of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: |- + Namespaces indicates namespaces from which Routes may be attached to this + Listener. This is restricted to the namespace of this Gateway by default. + + Support: Core + properties: + from: + default: Same + description: |- + From indicates where Routes will be selected for this Gateway. Possible + values are: + + * All: Routes in all namespaces may be used by this Gateway. + * Selector: Routes in namespaces selected by the selector may be used by + this Gateway. + * Same: Only Routes in the same namespace may be used by this Gateway. + + Support: Core + enum: + - All + - Selector + - Same + type: string + selector: + description: |- + Selector must be specified when From is set to "Selector". In that case, + only Routes in Namespaces matching this Selector will be selected by this + Gateway. This field is ignored for other values of "From". + + Support: Core + 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 + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + 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 + type: object + type: object + hostname: + description: |- + Hostname specifies the virtual hostname to match for protocol types that + define this concept. When unspecified, all hostnames are matched. This + field is ignored for protocols that don't require hostname based + matching. + + Implementations MUST apply Hostname matching appropriately for each of + the following protocols: + + * TLS: The Listener Hostname MUST match the SNI. + * HTTP: The Listener Hostname MUST match the Host header of the request. + * HTTPS: The Listener Hostname SHOULD match both the SNI and Host header. + Note that this does not require the SNI and Host header to be the same. + The semantics of this are described in more detail below. + + To ensure security, Section 11.1 of RFC-6066 emphasizes that server + implementations that rely on SNI hostname matching MUST also verify + hostnames within the application protocol. + + Section 9.1.2 of RFC-7540 provides a mechanism for servers to reject the + reuse of a connection by responding with the HTTP 421 Misdirected Request + status code. This indicates that the origin server has rejected the + request because it appears to have been misdirected. + + To detect misdirected requests, Gateways SHOULD match the authority of + the requests with all the SNI hostname(s) configured across all the + Gateway Listeners on the same port and protocol: + + * If another Listener has an exact match or more specific wildcard entry, + the Gateway SHOULD return a 421. + * If the current Listener (selected by SNI matching during ClientHello) + does not match the Host: + * If another Listener does match the Host the Gateway SHOULD return a + 421. + * If no other Listener matches the Host, the Gateway MUST return a + 404. + + For HTTPRoute and TLSRoute resources, there is an interaction with the + `spec.hostnames` array. When both listener and route specify hostnames, + there MUST be an intersection between the values for a Route to be + accepted. For more information, refer to the Route specific Hostnames + documentation. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + name: + description: |- + Name is the name of the Listener. This name MUST be unique within a + Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: |- + Port is the network port. Multiple listeners may use the + same port, subject to the Listener compatibility rules. + + Support: Core + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: |- + Protocol specifies the network protocol this listener expects to receive. + + Support: Core + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: |- + TLS is the TLS configuration for the Listener. This field is required if + the Protocol field is "HTTPS" or "TLS". It is invalid to set this field + if the Protocol field is "HTTP", "TCP", or "UDP". + + The association of SNIs to Certificate defined in GatewayTLSConfig is + defined based on the Hostname field for this listener. + + The GatewayClass MUST use the longest matching SNI out of all + available certificates for any TLS handshake. + + Support: Core + properties: + certificateRefs: + description: |- + CertificateRefs contains a series of references to Kubernetes objects that + contains TLS certificates and private keys. These certificates are used to + establish a TLS handshake for requests that match the hostname of the + associated listener. + + A single CertificateRef to a Kubernetes Secret has "Core" support. + Implementations MAY choose to support attaching multiple certificates to + a Listener, but this behavior is implementation-specific. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + + This field is required to have at least one element when the mode is set + to "Terminate" (default) and is optional otherwise. + + CertificateRefs can reference to standard Kubernetes resources, i.e. + Secret, or implementation-specific custom resources. + + Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls + + Support: Implementation-specific (More than one reference or other resource types) + items: + description: |- + SecretObjectReference identifies an API object including its namespace, + defaulting to Secret. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example + "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + mode: + default: Terminate + description: |- + Mode defines the TLS behavior for the TLS session initiated by the client. + There are two possible modes: + + - Terminate: The TLS session between the downstream client and the + Gateway is terminated at the Gateway. This mode requires certificates + to be specified in some way, such as populating the certificateRefs + field. + - Passthrough: The TLS session is NOT terminated by the Gateway. This + implies that the Gateway can't decipher the TLS stream except for + the ClientHello message of the TLS protocol. The certificateRefs field + is ignored in this mode. + + Support: Core + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Options are a list of key/value pairs to enable extended TLS + configuration for each implementation. For example, configuring the + minimum TLS version or supported cipher suites. + + A set of common keys MAY be defined by the API in the future. To avoid + any ambiguity, implementation-specific definitions MUST use + domain-prefixed names, such as `example.com/my-custom-option`. + Un-prefixed names are reserved for key names defined by Gateway API. + + Support: Implementation-specific + maxProperties: 16 + type: object + type: object + x-kubernetes-validations: + - message: certificateRefs or options must be specified when + mode is Terminate + rule: 'self.mode == ''Terminate'' ? size(self.certificateRefs) + > 0 || size(self.options) > 0 : true' + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: tls must not be specified for protocols ['HTTP', 'TCP', + 'UDP'] + rule: 'self.all(l, l.protocol in [''HTTP'', ''TCP'', ''UDP''] ? + !has(l.tls) : true)' + - message: tls mode must be Terminate for protocol HTTPS + rule: 'self.all(l, (l.protocol == ''HTTPS'' && has(l.tls)) ? (l.tls.mode + == '''' || l.tls.mode == ''Terminate'') : true)' + - message: hostname must not be specified for protocols ['TCP', 'UDP'] + rule: 'self.all(l, l.protocol in [''TCP'', ''UDP''] ? (!has(l.hostname) + || l.hostname == '''') : true)' + - message: Listener name must be unique within the Gateway + rule: self.all(l1, self.exists_one(l2, l1.name == l2.name)) + - message: Combination of port, protocol and hostname must be unique + for each listener + rule: 'self.all(l1, self.exists_one(l2, l1.port == l2.port && l1.protocol + == l2.protocol && (has(l1.hostname) && has(l2.hostname) ? l1.hostname + == l2.hostname : !has(l1.hostname) && !has(l2.hostname))))' + required: + - gatewayClassName + - listeners + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: Status defines the current state of Gateway. + properties: + addresses: + description: |- + Addresses lists the network addresses that have been bound to the + Gateway. + + This list may differ from the addresses provided in the spec under some + conditions: + + * no addresses are specified, all addresses are dynamically assigned + * a combination of specified and dynamic addresses are assigned + * a specified address was unusable (e.g. already in use) + items: + description: GatewayStatusAddress describes a network address that + is bound to a Gateway. + oneOf: + - properties: + type: + enum: + - IPAddress + value: + anyOf: + - format: ipv4 + - format: ipv6 + - properties: + type: + not: + enum: + - IPAddress + properties: + type: + default: IPAddress + description: Type of the address. + maxLength: 253 + minLength: 1 + pattern: ^Hostname|IPAddress|NamedAddress|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + value: + description: |- + Value of the address. The validity of the values will depend + on the type and support by the controller. + + Examples: `1.2.3.4`, `128::1`, `my-ip-address`. + maxLength: 253 + minLength: 1 + type: string + required: + - value + type: object + x-kubernetes-validations: + - message: Hostname value must only contain valid characters (matching + ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$) + rule: 'self.type == ''Hostname'' ? self.value.matches(r"""^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"""): + true' + maxItems: 16 + type: array + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: |- + Conditions describe the current conditions of the Gateway. + + Implementations should prefer to express Gateway conditions + using the `GatewayConditionType` and `GatewayConditionReason` + constants so that operators and tools can converge on a common + vocabulary to describe Gateway state. + + Known condition types are: + + * "Accepted" + * "Programmed" + * "Ready" + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port + defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: |- + AttachedRoutes represents the total number of Routes that have been + successfully attached to this Listener. + + Successful attachment of a Route to a Listener is based solely on the + combination of the AllowedRoutes field on the corresponding Listener + and the Route's ParentRefs field. A Route is successfully attached to + a Listener when it is selected by the Listener's AllowedRoutes field + AND the Route has a valid ParentRef selecting the whole Gateway + resource or a specific Listener as a parent resource (more detail on + attachment semantics can be found in the documentation on the various + Route kinds ParentRefs fields). Listener or Route status does not impact + successful attachment, i.e. the AttachedRoutes field count MUST be set + for Listeners with condition Accepted: false and MUST count successfully + attached Routes that may themselves have Accepted: false conditions. + + Uses for this field include troubleshooting Route attachment and + measuring blast radius/impact of changes to a Listener. + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this + listener. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status + corresponds to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + supportedKinds: + description: |- + SupportedKinds is the list indicating the Kinds supported by this + listener. This MUST represent the kinds an implementation supports for + that Listener configuration. + + If kinds are specified in Spec that are not supported, they MUST NOT + appear in this list and an implementation MUST set the "ResolvedRefs" + condition to "False" with the "InvalidRouteKinds" reason. If both valid + and invalid Route kinds are specified, the implementation MUST + reference the valid Route kinds that have been specified. + items: + description: RouteGroupKind indicates the group and kind of + a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/standard-gatewayapi-crds.yaml +# +# config/crd/standard/gateway.networking.k8s.io_grpcroutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: standard + creationTimestamp: null + name: grpcroutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: GRPCRoute + listKind: GRPCRouteList + plural: grpcroutes + singular: grpcroute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + GRPCRoute provides a way to route gRPC requests. This includes the capability + to match requests by hostname, gRPC service, gRPC method, or HTTP/2 header. + Filters can be used to specify additional processing steps. Backends specify + where matching requests will be routed. + + GRPCRoute falls under extended support within the Gateway API. Within the + following specification, the word "MUST" indicates that an implementation + supporting GRPCRoute must conform to the indicated requirement, but an + implementation not supporting this route type need not follow the requirement + unless explicitly indicated. + + Implementations supporting `GRPCRoute` with the `HTTPS` `ProtocolType` MUST + accept HTTP/2 connections without an initial upgrade from HTTP/1.1, i.e. via + ALPN. If the implementation does not support this, then it MUST set the + "Accepted" condition to "False" for the affected listener with a reason of + "UnsupportedProtocol". Implementations MAY also accept HTTP/2 connections + with an upgrade from HTTP/1. + + Implementations supporting `GRPCRoute` with the `HTTP` `ProtocolType` MUST + support HTTP/2 over cleartext TCP (h2c, + https://www.rfc-editor.org/rfc/rfc7540#section-3.1) without an initial + upgrade from HTTP/1.1, i.e. with prior knowledge + (https://www.rfc-editor.org/rfc/rfc7540#section-3.4). If the implementation + does not support this, then it MUST set the "Accepted" condition to "False" + for the affected listener with a reason of "UnsupportedProtocol". + Implementations MAY also accept HTTP/2 connections with an upgrade from + HTTP/1, i.e. without prior knowledge. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of GRPCRoute. + properties: + hostnames: + description: |- + Hostnames defines a set of hostnames to match against the GRPC + Host header to select a GRPCRoute to process the request. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label MUST appear by itself as the first label. + + If a hostname is specified by both the Listener and GRPCRoute, there + MUST be at least one intersecting hostname for the GRPCRoute to be + attached to the Listener. For example: + + * A Listener with `test.example.com` as the hostname matches GRPCRoutes + that have either not specified any hostnames, or have specified at + least one of `test.example.com` or `*.example.com`. + * A Listener with `*.example.com` as the hostname matches GRPCRoutes + that have either not specified any hostnames or have specified at least + one hostname that matches the Listener hostname. For example, + `test.example.com` and `*.example.com` would both match. On the other + hand, `example.com` and `test.example.net` would not match. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + + If both the Listener and GRPCRoute have specified hostnames, any + GRPCRoute hostnames that do not match the Listener hostname MUST be + ignored. For example, if a Listener specified `*.example.com`, and the + GRPCRoute specified `test.example.com` and `test.example.net`, + `test.example.net` MUST NOT be considered for a match. + + If both the Listener and GRPCRoute have specified hostnames, and none + match with the criteria above, then the GRPCRoute MUST NOT be accepted by + the implementation. The implementation MUST raise an 'Accepted' Condition + with a status of `False` in the corresponding RouteParentStatus. + + If a Route (A) of type HTTPRoute or GRPCRoute is attached to a + Listener and that listener already has another Route (B) of the other + type attached and the intersection of the hostnames of A and B is + non-empty, then the implementation MUST accept exactly one of these two + routes, determined by the following criteria, in order: + + * The oldest Route based on creation timestamp. + * The Route appearing first in alphabetical order by + "{namespace}/{name}". + + The rejected Route MUST raise an 'Accepted' condition with a status of + 'False' in the corresponding RouteParentStatus. + + Support: Core + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''')) : true))' + - message: sectionName must be unique when parentRefs includes 2 or + more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || (has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)))) + rules: + description: Rules are a list of GRPC matchers, filters and actions. + items: + description: |- + GRPCRouteRule defines the semantics for matching a gRPC request based on + conditions (matches), processing it (filters), and forwarding the request to + an API object (backendRefs). + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. + + Failure behavior here depends on how many BackendRefs are specified and + how many are invalid. + + If *all* entries in BackendRefs are invalid, and there are also no filters + specified in this route rule, *all* traffic which matches this rule MUST + receive an `UNAVAILABLE` status. + + See the GRPCBackendRef definition for the rules about what makes a single + GRPCBackendRef invalid. + + When a GRPCBackendRef is invalid, `UNAVAILABLE` statuses MUST be returned for + requests that would have otherwise been routed to an invalid backend. If + multiple backends are specified, and some are invalid, the proportion of + requests that would otherwise have been routed to an invalid backend + MUST receive an `UNAVAILABLE` status. + + For example, if two backends are specified with equal weights, and one is + invalid, 50 percent of traffic MUST receive an `UNAVAILABLE` status. + Implementations may choose how that 50 percent is determined. + + Support: Core for Kubernetes Service + + Support: Implementation-specific for any other resource + + Support for weight: Core + items: + description: |- + GRPCBackendRef defines how a GRPCRoute forwards a gRPC request. + + Note that when a namespace different than the local namespace is specified, a + ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + properties: + filters: + description: |- + Filters defined at this level MUST be executed if and only if the + request is being forwarded to the backend defined here. + + Support: Implementation-specific (For broader support of filters, use the + Filters field in GRPCRouteRule.) + items: + description: |- + GRPCRouteFilter defines processing steps that must be completed during the + request or response lifecycle. GRPCRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + Support: Implementation-specific + + This filter can be used multiple times within the same rule. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal + to denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be + specified in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations supporting GRPCRoute MUST support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` MUST be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + enum: + - ResponseHeaderModifier + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil + if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type + != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type + == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil + if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type + != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for + RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == + ''RequestMirror'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for + ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + filters: + description: |- + Filters define the filters that are applied to requests that match + this rule. + + The effects of ordering of multiple behaviors are currently unspecified. + This can change in the future based on feedback during the alpha stage. + + Conformance-levels at this level are defined based on the type of filter: + + - ALL core filters MUST be supported by all implementations that support + GRPCRoute. + - Implementers are encouraged to support extended filters. + - Implementation-specific custom filters have no API guarantees across + implementations. + + Specifying the same filter multiple times is not supported unless explicitly + indicated in the filter. + + If an implementation cannot support a combination of filters, it must clearly + document that limitation. In cases where incompatible or unsupported + filters are specified and cause the `Accepted` condition to be set to status + `False`, implementations may use the `IncompatibleFilters` reason to specify + this configuration error. + + Support: Core + items: + description: |- + GRPCRouteFilter defines processing steps that must be completed during the + request or response lifecycle. GRPCRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + Support: Implementation-specific + + This filter can be used multiple times within the same rule. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal to + denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be specified + in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations supporting GRPCRoute MUST support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` MUST be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + enum: + - ResponseHeaderModifier + - RequestHeaderModifier + - RequestMirror + - ExtensionRef + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the + filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != + ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == + ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the + filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != + ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror + filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef + filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + matches: + description: |- + Matches define conditions used for matching the rule against incoming + gRPC requests. Each match is independent, i.e. this rule will be matched + if **any** one of the matches is satisfied. + + For example, take the following matches configuration: + + ``` + matches: + - method: + service: foo.bar + headers: + values: + version: 2 + - method: + service: foo.bar.v2 + ``` + + For a request to match against this rule, it MUST satisfy + EITHER of the two conditions: + + - service of foo.bar AND contains the header `version: 2` + - service of foo.bar.v2 + + See the documentation for GRPCRouteMatch on how to specify multiple + match conditions to be ANDed together. + + If no matches are specified, the implementation MUST match every gRPC request. + + Proxy or Load Balancer routing configuration generated from GRPCRoutes + MUST prioritize rules based on the following criteria, continuing on + ties. Merging MUST not be done between GRPCRoutes and HTTPRoutes. + Precedence MUST be given to the rule with the largest number of: + + * Characters in a matching non-wildcard hostname. + * Characters in a matching hostname. + * Characters in a matching service. + * Characters in a matching method. + * Header matches. + + If ties still exist across multiple Routes, matching precedence MUST be + determined in order of the following criteria, continuing on ties: + + * The oldest Route based on creation timestamp. + * The Route appearing first in alphabetical order by + "{namespace}/{name}". + + If ties still exist within the Route that has been given precedence, + matching precedence MUST be granted to the first matching rule meeting + the above criteria. + items: + description: |- + GRPCRouteMatch defines the predicate used to match requests to a given + action. Multiple match types are ANDed together, i.e. the match will + evaluate to true only if all conditions are satisfied. + + For example, the match below will match a gRPC request only if its service + is `foo` AND it contains the `version: v1` header: + + ``` + matches: + - method: + type: Exact + service: "foo" + headers: + - name: "version" + value "v1" + + ``` + properties: + headers: + description: |- + Headers specifies gRPC request header matchers. Multiple match values are + ANDed together, meaning, a request MUST match all the specified headers + to select the route. + items: + description: |- + GRPCHeaderMatch describes how to select a gRPC route by matching gRPC request + headers. + properties: + name: + description: |- + Name is the name of the gRPC Header to be matched. + + If multiple entries specify equivalent header names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: Type specifies how to match against + the value of the header. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of the gRPC Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: |- + Method specifies a gRPC request service/method matcher. If this field is + not specified, all services and methods will match. + properties: + method: + description: |- + Value of the method to match against. If left empty or omitted, will + match all services. + + At least one of Service and Method MUST be a non-empty string. + maxLength: 1024 + type: string + service: + description: |- + Value of the service to match against. If left empty or omitted, will + match any service. + + At least one of Service and Method MUST be a non-empty string. + maxLength: 1024 + type: string + type: + default: Exact + description: |- + Type specifies how to match against the service and/or method. + Support: Core (Exact with service and method specified) + + Support: Implementation-specific (Exact with method specified but no service specified) + + Support: Implementation-specific (RegularExpression) + enum: + - Exact + - RegularExpression + type: string + type: object + x-kubernetes-validations: + - message: One or both of 'service' or 'method' must be + specified + rule: 'has(self.type) ? has(self.service) || has(self.method) + : true' + - message: service must only contain valid characters + (matching ^(?i)\.?[a-z_][a-z_0-9]*(\.[a-z_][a-z_0-9]*)*$) + rule: '(!has(self.type) || self.type == ''Exact'') && + has(self.service) ? self.service.matches(r"""^(?i)\.?[a-z_][a-z_0-9]*(\.[a-z_][a-z_0-9]*)*$"""): + true' + - message: method must only contain valid characters (matching + ^[A-Za-z_][A-Za-z_0-9]*$) + rule: '(!has(self.type) || self.type == ''Exact'') && + has(self.method) ? self.method.matches(r"""^[A-Za-z_][A-Za-z_0-9]*$"""): + true' + type: object + maxItems: 64 + type: array + type: object + maxItems: 16 + type: array + x-kubernetes-validations: + - message: While 16 rules and 64 matches per rule are allowed, the + total number of matches across all rules in a route must be less + than 128 + rule: '(self.size() > 0 ? (has(self[0].matches) ? self[0].matches.size() + : 0) : 0) + (self.size() > 1 ? (has(self[1].matches) ? self[1].matches.size() + : 0) : 0) + (self.size() > 2 ? (has(self[2].matches) ? self[2].matches.size() + : 0) : 0) + (self.size() > 3 ? (has(self[3].matches) ? self[3].matches.size() + : 0) : 0) + (self.size() > 4 ? (has(self[4].matches) ? self[4].matches.size() + : 0) : 0) + (self.size() > 5 ? (has(self[5].matches) ? self[5].matches.size() + : 0) : 0) + (self.size() > 6 ? (has(self[6].matches) ? self[6].matches.size() + : 0) : 0) + (self.size() > 7 ? (has(self[7].matches) ? self[7].matches.size() + : 0) : 0) + (self.size() > 8 ? (has(self[8].matches) ? self[8].matches.size() + : 0) : 0) + (self.size() > 9 ? (has(self[9].matches) ? self[9].matches.size() + : 0) : 0) + (self.size() > 10 ? (has(self[10].matches) ? self[10].matches.size() + : 0) : 0) + (self.size() > 11 ? (has(self[11].matches) ? self[11].matches.size() + : 0) : 0) + (self.size() > 12 ? (has(self[12].matches) ? self[12].matches.size() + : 0) : 0) + (self.size() > 13 ? (has(self[13].matches) ? self[13].matches.size() + : 0) : 0) + (self.size() > 14 ? (has(self[14].matches) ? self[14].matches.size() + : 0) : 0) + (self.size() > 15 ? (has(self[15].matches) ? self[15].matches.size() + : 0) : 0) <= 128' + type: object + status: + description: Status defines the current state of GRPCRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/standard-gatewayapi-crds.yaml +# +# config/crd/standard/gateway.networking.k8s.io_httproutes.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: standard + creationTimestamp: null + name: httproutes.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: HTTPRoute + listKind: HTTPRouteList + plural: httproutes + singular: httproute + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + HTTPRoute provides a way to route HTTP requests. This includes the capability + to match requests by hostname, path, header, or query param. Filters can be + used to specify additional processing steps. Backends specify where matching + requests should be routed. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of HTTPRoute. + properties: + hostnames: + description: |- + Hostnames defines a set of hostnames that should match against the HTTP Host + header to select a HTTPRoute used to process the request. Implementations + MUST ignore any port value specified in the HTTP Host header while + performing a match and (absent of any applicable header modification + configuration) MUST forward this header unmodified to the backend. + + Valid values for Hostnames are determined by RFC 1123 definition of a + hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + If a hostname is specified by both the Listener and HTTPRoute, there + must be at least one intersecting hostname for the HTTPRoute to be + attached to the Listener. For example: + + * A Listener with `test.example.com` as the hostname matches HTTPRoutes + that have either not specified any hostnames, or have specified at + least one of `test.example.com` or `*.example.com`. + * A Listener with `*.example.com` as the hostname matches HTTPRoutes + that have either not specified any hostnames or have specified at least + one hostname that matches the Listener hostname. For example, + `*.example.com`, `test.example.com`, and `foo.test.example.com` would + all match. On the other hand, `example.com` and `test.example.net` would + not match. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + + If both the Listener and HTTPRoute have specified hostnames, any + HTTPRoute hostnames that do not match the Listener hostname MUST be + ignored. For example, if a Listener specified `*.example.com`, and the + HTTPRoute specified `test.example.com` and `test.example.net`, + `test.example.net` must not be considered for a match. + + If both the Listener and HTTPRoute have specified hostnames, and none + match with the criteria above, then the HTTPRoute is not accepted. The + implementation must raise an 'Accepted' Condition with a status of + `False` in the corresponding RouteParentStatus. + + In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. + overlapping wildcard matching and exact matching hostnames), precedence must + be given to rules from the HTTPRoute with the largest number of: + + * Characters in a matching non-wildcard hostname. + * Characters in a matching hostname. + + If ties exist across multiple Routes, the matching precedence rules for + HTTPRouteMatches takes over. + + Support: Core + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''')) : true))' + - message: sectionName must be unique when parentRefs includes 2 or + more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || (has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)))) + rules: + default: + - matches: + - path: + type: PathPrefix + value: / + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: |- + HTTPRouteRule defines semantics for matching an HTTP request based on + conditions (matches), processing it (filters), and forwarding the request to + an API object (backendRefs). + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. + + Failure behavior here depends on how many BackendRefs are specified and + how many are invalid. + + If *all* entries in BackendRefs are invalid, and there are also no filters + specified in this route rule, *all* traffic which matches this rule MUST + receive a 500 status code. + + See the HTTPBackendRef definition for the rules about what makes a single + HTTPBackendRef invalid. + + When a HTTPBackendRef is invalid, 500 status codes MUST be returned for + requests that would have otherwise been routed to an invalid backend. If + multiple backends are specified, and some are invalid, the proportion of + requests that would otherwise have been routed to an invalid backend + MUST receive a 500 status code. + + For example, if two backends are specified with equal weights, and one is + invalid, 50 percent of traffic must receive a 500. Implementations may + choose how that 50 percent is determined. + + When a HTTPBackendRef refers to a Service that has no ready endpoints, + implementations SHOULD return a 503 for requests to that backend instead. + If an implementation chooses to do this, all of the above rules for 500 responses + MUST also apply for responses that return a 503. + + Support: Core for Kubernetes Service + + Support: Extended for Kubernetes ServiceImport + + Support: Implementation-specific for any other resource + + Support for weight: Core + items: + description: |- + HTTPBackendRef defines how a HTTPRoute forwards a HTTP request. + + Note that when a namespace different than the local namespace is specified, a + ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + properties: + filters: + description: |- + Filters defined at this level should be executed if and only if the + request is being forwarded to the backend defined here. + + Support: Implementation-specific (For broader support of filters, use the + Filters field in HTTPRouteRule.) + items: + description: |- + HTTPRouteFilter defines processing steps that must be completed during the + request or response lifecycle. HTTPRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + This filter can be used multiple times within the same rule. + + Support: Implementation-specific + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal + to denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be + specified in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + requestRedirect: + description: |- + RequestRedirect defines a schema for a filter that responds to the + request with an HTTP redirection. + + Support: Core + properties: + hostname: + description: |- + Hostname is the hostname to be used in the value of the `Location` + header in the response. + When empty, the hostname in the `Host` header of the request is used. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines parameters used to modify the path of the incoming request. + The modified path is then used to construct the `Location` header. When + empty, the request path is used as-is. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified + when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? + has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when + replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type + == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified + when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' + ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' + when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + port: + description: |- + Port is the port to be used in the value of the `Location` + header in the response. + + If no port is specified, the redirect port MUST be derived using the + following rules: + + * If redirect scheme is not-empty, the redirect port MUST be the well-known + port associated with the redirect scheme. Specifically "http" to port 80 + and "https" to port 443. If the redirect scheme does not have a + well-known port, the listener port of the Gateway SHOULD be used. + * If redirect scheme is empty, the redirect port MUST be the Gateway + Listener port. + + Implementations SHOULD NOT add the port number in the 'Location' + header in the following cases: + + * A Location header that will use HTTP (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 80. + * A Location header that will use HTTPS (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 443. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: |- + Scheme is the scheme to be used in the value of the `Location` header in + the response. When empty, the scheme of the request is used. + + Scheme redirects can affect the port of the redirect, for more information, + refer to the documentation for the port field of this filter. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Extended + enum: + - http + - https + type: string + statusCode: + default: 302 + description: |- + StatusCode is the HTTP status code to be used in response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Core + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations must support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by + specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` should be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: |- + URLRewrite defines a schema for a filter that modifies a request during forwarding. + + Support: Extended + properties: + hostname: + description: |- + Hostname is the value to be used to replace the Host header value during + forwarding. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines a path rewrite. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified + when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? + has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when + replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type + == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified + when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' + ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' + when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil + if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type + != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type + == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil + if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type + != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for + RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == + ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the + filter.type is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != + ''RequestRedirect'')' + - message: filter.requestRedirect must be specified + for RequestRedirect filter.type + rule: '!(!has(self.requestRedirect) && self.type == + ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type + is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite + filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for + ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') + && self.exists(f, f.type == ''URLRewrite''))' + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') + && self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() + <= 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() + <= 1 + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + filters: + description: |- + Filters define the filters that are applied to requests that match + this rule. + + Wherever possible, implementations SHOULD implement filters in the order + they are specified. + + Implementations MAY choose to implement this ordering strictly, rejecting + any combination or order of filters that cannot be supported. If implementations + choose a strict interpretation of filter ordering, they MUST clearly document + that behavior. + + To reject an invalid combination or order of filters, implementations SHOULD + consider the Route Rules with this configuration invalid. If all Route Rules + in a Route are invalid, the entire Route would be considered invalid. If only + a portion of Route Rules are invalid, implementations MUST set the + "PartiallyInvalid" condition for the Route. + + Conformance-levels at this level are defined based on the type of filter: + + - ALL core filters MUST be supported by all implementations. + - Implementers are encouraged to support extended filters. + - Implementation-specific custom filters have no API guarantees across + implementations. + + Specifying the same filter multiple times is not supported unless explicitly + indicated in the filter. + + All filters are expected to be compatible with each other except for the + URLRewrite and RequestRedirect filters, which may not be combined. If an + implementation cannot support other combinations of filters, they must clearly + document that limitation. In cases where incompatible or unsupported + filters are specified and cause the `Accepted` condition to be set to status + `False`, implementations may use the `IncompatibleFilters` reason to specify + this configuration error. + + Support: Core + items: + description: |- + HTTPRouteFilter defines processing steps that must be completed during the + request or response lifecycle. HTTPRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + This filter can be used multiple times within the same rule. + + Support: Implementation-specific + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal to + denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be specified + in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + requestRedirect: + description: |- + RequestRedirect defines a schema for a filter that responds to the + request with an HTTP redirection. + + Support: Core + properties: + hostname: + description: |- + Hostname is the hostname to be used in the value of the `Location` + header in the response. + When empty, the hostname in the `Host` header of the request is used. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines parameters used to modify the path of the incoming request. + The modified path is then used to construct the `Location` header. When + empty, the request path is used as-is. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when + type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) + : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath + is set + rule: 'has(self.replaceFullPath) ? self.type == + ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when + type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) + : true' + - message: type must be 'ReplacePrefixMatch' when + replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + port: + description: |- + Port is the port to be used in the value of the `Location` + header in the response. + + If no port is specified, the redirect port MUST be derived using the + following rules: + + * If redirect scheme is not-empty, the redirect port MUST be the well-known + port associated with the redirect scheme. Specifically "http" to port 80 + and "https" to port 443. If the redirect scheme does not have a + well-known port, the listener port of the Gateway SHOULD be used. + * If redirect scheme is empty, the redirect port MUST be the Gateway + Listener port. + + Implementations SHOULD NOT add the port number in the 'Location' + header in the following cases: + + * A Location header that will use HTTP (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 80. + * A Location header that will use HTTPS (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 443. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: |- + Scheme is the scheme to be used in the value of the `Location` header in + the response. When empty, the scheme of the request is used. + + Scheme redirects can affect the port of the redirect, for more information, + refer to the documentation for the port field of this filter. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Extended + enum: + - http + - https + type: string + statusCode: + default: 302 + description: |- + StatusCode is the HTTP status code to be used in response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Core + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations must support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by + specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` should be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: |- + URLRewrite defines a schema for a filter that modifies a request during forwarding. + + Support: Extended + properties: + hostname: + description: |- + Hostname is the value to be used to replace the Host header value during + forwarding. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines a path rewrite. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when + type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) + : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath + is set + rule: 'has(self.replaceFullPath) ? self.type == + ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when + type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) + : true' + - message: type must be 'ReplacePrefixMatch' when + replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the + filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != + ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == + ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the + filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != + ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror + filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the filter.type + is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')' + - message: filter.requestRedirect must be specified for RequestRedirect + filter.type + rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type + is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite + filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef + filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') && + self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() <= + 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() <= 1 + matches: + default: + - path: + type: PathPrefix + value: / + description: |- + Matches define conditions used for matching the rule against incoming + HTTP requests. Each match is independent, i.e. this rule will be matched + if **any** one of the matches is satisfied. + + For example, take the following matches configuration: + + ``` + matches: + - path: + value: "/foo" + headers: + - name: "version" + value: "v2" + - path: + value: "/v2/foo" + ``` + + For a request to match against this rule, a request must satisfy + EITHER of the two conditions: + + - path prefixed with `/foo` AND contains the header `version: v2` + - path prefix of `/v2/foo` + + See the documentation for HTTPRouteMatch on how to specify multiple + match conditions that should be ANDed together. + + If no matches are specified, the default is a prefix + path match on "/", which has the effect of matching every + HTTP request. + + Proxy or Load Balancer routing configuration generated from HTTPRoutes + MUST prioritize matches based on the following criteria, continuing on + ties. Across all rules specified on applicable Routes, precedence must be + given to the match having: + + * "Exact" path match. + * "Prefix" path match with largest number of characters. + * Method match. + * Largest number of header matches. + * Largest number of query param matches. + + Note: The precedence of RegularExpression path matches are implementation-specific. + + If ties still exist across multiple Routes, matching precedence MUST be + determined in order of the following criteria, continuing on ties: + + * The oldest Route based on creation timestamp. + * The Route appearing first in alphabetical order by + "{namespace}/{name}". + + If ties still exist within an HTTPRoute, matching precedence MUST be granted + to the FIRST matching rule (in list order) with a match meeting the above + criteria. + + When no rules matching a request have been successfully attached to the + parent a request is coming from, a HTTP 404 status code MUST be returned. + items: + description: "HTTPRouteMatch defines the predicate used to + match requests to a given\naction. Multiple match types + are ANDed together, i.e. the match will\nevaluate to true + only if all conditions are satisfied.\n\nFor example, the + match below will match a HTTP request only if its path\nstarts + with `/foo` AND it contains the `version: v1` header:\n\n```\nmatch:\n\n\tpath:\n\t + \ value: \"/foo\"\n\theaders:\n\t- name: \"version\"\n\t + \ value \"v1\"\n\n```" + properties: + headers: + description: |- + Headers specifies HTTP request header matchers. Multiple match values are + ANDed together, meaning, a request must match all the specified headers + to select the route. + items: + description: |- + HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request + headers. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + + When a header is repeated in an HTTP request, it is + implementation-specific behavior as to how this is represented. + Generally, proxies should follow the guidance from the RFC: + https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding + processing a repeated header, with special handling for "Set-Cookie". + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the header. + + Support: Core (Exact) + + Support: Implementation-specific (RegularExpression) + + Since RegularExpression HeaderMatchType has implementation-specific + conformance, implementations can support POSIX, PCRE or any other dialects + of regular expressions. Please read the implementation's documentation to + determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP Header to + be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: |- + Method specifies HTTP method matcher. + When specified, this route will be matched only if the request has the + specified method. + + Support: Extended + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: |- + Path specifies a HTTP request path matcher. If this field is not + specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: |- + Type specifies how to match against the path Value. + + Support: Core (Exact, PathPrefix) + + Support: Implementation-specific (RegularExpression) + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path to match against. + maxLength: 1024 + type: string + type: object + x-kubernetes-validations: + - message: value must be an absolute path and start with + '/' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.startsWith(''/'') + : true' + - message: must not contain '//' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''//'') + : true' + - message: must not contain '/./' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/./'') + : true' + - message: must not contain '/../' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/../'') + : true' + - message: must not contain '%2f' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2f'') + : true' + - message: must not contain '%2F' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2F'') + : true' + - message: must not contain '#' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''#'') + : true' + - message: must not end with '/..' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/..'') + : true' + - message: must not end with '/.' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/.'') + : true' + - message: type must be one of ['Exact', 'PathPrefix', + 'RegularExpression'] + rule: self.type in ['Exact','PathPrefix'] || self.type + == 'RegularExpression' + - message: must only contain valid characters (matching + ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$) + for types ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.matches(r"""^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$""") + : true' + queryParams: + description: |- + QueryParams specifies HTTP query parameter matchers. Multiple match + values are ANDed together, meaning, a request must match all the + specified query parameters to select the route. + + Support: Extended + items: + description: |- + HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP + query parameters. + properties: + name: + description: |- + Name is the name of the HTTP query param to be matched. This must be an + exact string match. (See + https://tools.ietf.org/html/rfc7230#section-2.7.3). + + If multiple entries specify equivalent query param names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent query param name MUST be ignored. + + If a query param is repeated in an HTTP request, the behavior is + purposely left undefined, since different data planes have different + capabilities. However, it is *recommended* that implementations should + match against the first value of the param if the data plane supports it, + as this behavior is expected in other load balancing contexts outside of + the Gateway API. + + Users SHOULD NOT route traffic based on repeated query params to guard + themselves against potential differences in the implementations. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the query parameter. + + Support: Extended (Exact) + + Support: Implementation-specific (RegularExpression) + + Since RegularExpression QueryParamMatchType has Implementation-specific + conformance, implementations can support POSIX, PCRE or any other + dialects of regular expressions. Please read the implementation's + documentation to determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP query param + to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 64 + type: array + timeouts: + description: |- + Timeouts defines the timeouts that can be configured for an HTTP request. + + Support: Extended + properties: + backendRequest: + description: |- + BackendRequest specifies a timeout for an individual request from the gateway + to a backend. This covers the time from when the request first starts being + sent from the gateway to when the full response has been received from the backend. + + Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout + completely. Implementations that cannot completely disable the timeout MUST + instead interpret the zero duration as the longest possible value to which + the timeout can be set. + + An entire client HTTP transaction with a gateway, covered by the Request timeout, + may result in more than one call from the gateway to the destination backend, + for example, if automatic retries are supported. + + The value of BackendRequest must be a Gateway API Duration string as defined by + GEP-2257. When this field is unspecified, its behavior is implementation-specific; + when specified, the value of BackendRequest must be no more than the value of the + Request timeout (since the Request timeout encompasses the BackendRequest timeout). + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + request: + description: |- + Request specifies the maximum duration for a gateway to respond to an HTTP request. + If the gateway has not been able to respond before this deadline is met, the gateway + MUST return a timeout error. + + For example, setting the `rules.timeouts.request` field to the value `10s` in an + `HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds + to complete. + + Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout + completely. Implementations that cannot completely disable the timeout MUST + instead interpret the zero duration as the longest possible value to which + the timeout can be set. + + This timeout is intended to cover as close to the whole request-response transaction + as possible although an implementation MAY choose to start the timeout after the entire + request stream has been received instead of immediately after the transaction is + initiated by the client. + + The value of Request is a Gateway API Duration string as defined by GEP-2257. When this + field is unspecified, request timeout behavior is implementation-specific. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + x-kubernetes-validations: + - message: backendRequest timeout cannot be longer than request + timeout + rule: '!(has(self.request) && has(self.backendRequest) && + duration(self.request) != duration(''0s'') && duration(self.backendRequest) + > duration(self.request))' + type: object + x-kubernetes-validations: + - message: RequestRedirect filter must not be used together with + backendRefs + rule: '(has(self.backendRefs) && size(self.backendRefs) > 0) ? + (!has(self.filters) || self.filters.all(f, !has(f.requestRedirect))): + true' + - message: When using RequestRedirect filter with path.replacePrefixMatch, + exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.requestRedirect) + && has(f.requestRedirect.path) && f.requestRedirect.path.type + == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) + ? ((size(self.matches) != 1 || !has(self.matches[0].path) || + self.matches[0].path.type != ''PathPrefix'') ? false : true) + : true' + - message: When using URLRewrite filter with path.replacePrefixMatch, + exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.urlRewrite) + && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' + && has(f.urlRewrite.path.replacePrefixMatch))) ? ((size(self.matches) + != 1 || !has(self.matches[0].path) || self.matches[0].path.type + != ''PathPrefix'') ? false : true) : true' + - message: Within backendRefs, when using RequestRedirect filter + with path.replacePrefixMatch, exactly one PathPrefix match must + be specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, + (has(b.filters) && b.filters.exists_one(f, has(f.requestRedirect) + && has(f.requestRedirect.path) && f.requestRedirect.path.type + == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) + )) ? ((size(self.matches) != 1 || !has(self.matches[0].path) + || self.matches[0].path.type != ''PathPrefix'') ? false : true) + : true' + - message: Within backendRefs, When using URLRewrite filter with + path.replacePrefixMatch, exactly one PathPrefix match must be + specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, + (has(b.filters) && b.filters.exists_one(f, has(f.urlRewrite) + && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' + && has(f.urlRewrite.path.replacePrefixMatch))) )) ? ((size(self.matches) + != 1 || !has(self.matches[0].path) || self.matches[0].path.type + != ''PathPrefix'') ? false : true) : true' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: While 16 rules and 64 matches per rule are allowed, the + total number of matches across all rules in a route must be less + than 128 + rule: '(self.size() > 0 ? self[0].matches.size() : 0) + (self.size() + > 1 ? self[1].matches.size() : 0) + (self.size() > 2 ? self[2].matches.size() + : 0) + (self.size() > 3 ? self[3].matches.size() : 0) + (self.size() + > 4 ? self[4].matches.size() : 0) + (self.size() > 5 ? self[5].matches.size() + : 0) + (self.size() > 6 ? self[6].matches.size() : 0) + (self.size() + > 7 ? self[7].matches.size() : 0) + (self.size() > 8 ? self[8].matches.size() + : 0) + (self.size() > 9 ? self[9].matches.size() : 0) + (self.size() + > 10 ? self[10].matches.size() : 0) + (self.size() > 11 ? self[11].matches.size() + : 0) + (self.size() > 12 ? self[12].matches.size() : 0) + (self.size() + > 13 ? self[13].matches.size() : 0) + (self.size() > 14 ? self[14].matches.size() + : 0) + (self.size() > 15 ? self[15].matches.size() : 0) <= 128' + type: object + status: + description: Status defines the current state of HTTPRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.hostnames + name: Hostnames + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: |- + HTTPRoute provides a way to route HTTP requests. This includes the capability + to match requests by hostname, path, header, or query param. Filters can be + used to specify additional processing steps. Backends specify where matching + requests should be routed. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of HTTPRoute. + properties: + hostnames: + description: |- + Hostnames defines a set of hostnames that should match against the HTTP Host + header to select a HTTPRoute used to process the request. Implementations + MUST ignore any port value specified in the HTTP Host header while + performing a match and (absent of any applicable header modification + configuration) MUST forward this header unmodified to the backend. + + Valid values for Hostnames are determined by RFC 1123 definition of a + hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + If a hostname is specified by both the Listener and HTTPRoute, there + must be at least one intersecting hostname for the HTTPRoute to be + attached to the Listener. For example: + + * A Listener with `test.example.com` as the hostname matches HTTPRoutes + that have either not specified any hostnames, or have specified at + least one of `test.example.com` or `*.example.com`. + * A Listener with `*.example.com` as the hostname matches HTTPRoutes + that have either not specified any hostnames or have specified at least + one hostname that matches the Listener hostname. For example, + `*.example.com`, `test.example.com`, and `foo.test.example.com` would + all match. On the other hand, `example.com` and `test.example.net` would + not match. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + + If both the Listener and HTTPRoute have specified hostnames, any + HTTPRoute hostnames that do not match the Listener hostname MUST be + ignored. For example, if a Listener specified `*.example.com`, and the + HTTPRoute specified `test.example.com` and `test.example.net`, + `test.example.net` must not be considered for a match. + + If both the Listener and HTTPRoute have specified hostnames, and none + match with the criteria above, then the HTTPRoute is not accepted. The + implementation must raise an 'Accepted' Condition with a status of + `False` in the corresponding RouteParentStatus. + + In the event that multiple HTTPRoutes specify intersecting hostnames (e.g. + overlapping wildcard matching and exact matching hostnames), precedence must + be given to rules from the HTTPRoute with the largest number of: + + * Characters in a matching non-wildcard hostname. + * Characters in a matching hostname. + + If ties exist across multiple Routes, the matching precedence rules for + HTTPRouteMatches takes over. + + Support: Core + items: + description: |- + Hostname is the fully qualified domain name of a network host. This matches + the RFC 1123 definition of a hostname with 2 notable exceptions: + + 1. IPs are not allowed. + 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + label must appear by itself as the first label. + + Hostname can be "precise" which is a domain name without the terminating + dot of a network host (e.g. "foo.example.com") or "wildcard", which is a + domain name prefixed with a single wildcard label (e.g. `*.example.com`). + + Note that as per RFC1035 and RFC1123, a *label* must consist of lower case + alphanumeric characters or '-', and must start and end with an alphanumeric + character. No other punctuation is allowed. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + maxItems: 16 + type: array + parentRefs: + description: |- + ParentRefs references the resources (usually Gateways) that a Route wants + to be attached to. Note that the referenced parent resource needs to + allow this for the attachment to be complete. For Gateways, that means + the Gateway needs to allow attachment from Routes of this kind and + namespace. For Services, that means the Service must either be in the same + namespace for a "producer" route, or the mesh implementation must support + and allow "consumer" routes for the referenced Service. ReferenceGrant is + not applicable for governing ParentRefs to Services - it is not possible to + create a "producer" route for a Service in a different namespace from the + Route. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + ParentRefs must be _distinct_. This means either that: + + * They select different objects. If this is the case, then parentRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, `namespace`, and `name` must + be unique across all parentRef entries in the Route. + * They do not select different objects, but for each optional field used, + each ParentRef that selects the same object must set the same set of + optional fields to different values. If one ParentRef sets a + combination of optional fields, all must set the same combination. + + Some examples: + + * If one ParentRef sets `sectionName`, all ParentRefs referencing the + same object must also set `sectionName`. + * If one ParentRef sets `port`, all ParentRefs referencing the same + object must also set `port`. + * If one ParentRef sets `sectionName` and `port`, all ParentRefs + referencing the same object must also set `sectionName` and `port`. + + It is possible to separately reference multiple distinct objects that may + be collapsed by an implementation. For example, some implementations may + choose to merge compatible Gateway Listeners together. If that is the + case, the list of routes attached to those resources should also be + merged. + + Note that for ParentRefs that cross namespace boundaries, there are specific + rules. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example, + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable other kinds of cross-namespace reference. + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + maxItems: 32 + type: array + x-kubernetes-validations: + - message: sectionName must be specified when parentRefs includes + 2 or more references to the same parent + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '''') && (!has(p2.__namespace__) || p2.__namespace__ + == '''')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) ? ((!has(p1.sectionName) + || p1.sectionName == '''') == (!has(p2.sectionName) || p2.sectionName + == '''')) : true))' + - message: sectionName must be unique when parentRefs includes 2 or + more references to the same parent + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.__namespace__) + || p1.__namespace__ == '') && (!has(p2.__namespace__) || p2.__namespace__ + == '')) || (has(p1.__namespace__) && has(p2.__namespace__) && + p1.__namespace__ == p2.__namespace__ )) && (((!has(p1.sectionName) + || p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || (has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)))) + rules: + default: + - matches: + - path: + type: PathPrefix + value: / + description: Rules are a list of HTTP matchers, filters and actions. + items: + description: |- + HTTPRouteRule defines semantics for matching an HTTP request based on + conditions (matches), processing it (filters), and forwarding the request to + an API object (backendRefs). + properties: + backendRefs: + description: |- + BackendRefs defines the backend(s) where matching requests should be + sent. + + Failure behavior here depends on how many BackendRefs are specified and + how many are invalid. + + If *all* entries in BackendRefs are invalid, and there are also no filters + specified in this route rule, *all* traffic which matches this rule MUST + receive a 500 status code. + + See the HTTPBackendRef definition for the rules about what makes a single + HTTPBackendRef invalid. + + When a HTTPBackendRef is invalid, 500 status codes MUST be returned for + requests that would have otherwise been routed to an invalid backend. If + multiple backends are specified, and some are invalid, the proportion of + requests that would otherwise have been routed to an invalid backend + MUST receive a 500 status code. + + For example, if two backends are specified with equal weights, and one is + invalid, 50 percent of traffic must receive a 500. Implementations may + choose how that 50 percent is determined. + + When a HTTPBackendRef refers to a Service that has no ready endpoints, + implementations SHOULD return a 503 for requests to that backend instead. + If an implementation chooses to do this, all of the above rules for 500 responses + MUST also apply for responses that return a 503. + + Support: Core for Kubernetes Service + + Support: Extended for Kubernetes ServiceImport + + Support: Implementation-specific for any other resource + + Support for weight: Core + items: + description: |- + HTTPBackendRef defines how a HTTPRoute forwards a HTTP request. + + Note that when a namespace different than the local namespace is specified, a + ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + properties: + filters: + description: |- + Filters defined at this level should be executed if and only if the + request is being forwarded to the backend defined here. + + Support: Implementation-specific (For broader support of filters, use the + Filters field in HTTPRouteRule.) + items: + description: |- + HTTPRouteFilter defines processing steps that must be completed during the + request or response lifecycle. HTTPRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + This filter can be used multiple times within the same rule. + + Support: Implementation-specific + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind + == ''Service'') ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal + to denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be + specified in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + requestRedirect: + description: |- + RequestRedirect defines a schema for a filter that responds to the + request with an HTTP redirection. + + Support: Core + properties: + hostname: + description: |- + Hostname is the hostname to be used in the value of the `Location` + header in the response. + When empty, the hostname in the `Host` header of the request is used. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines parameters used to modify the path of the incoming request. + The modified path is then used to construct the `Location` header. When + empty, the request path is used as-is. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified + when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? + has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when + replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type + == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified + when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' + ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' + when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + port: + description: |- + Port is the port to be used in the value of the `Location` + header in the response. + + If no port is specified, the redirect port MUST be derived using the + following rules: + + * If redirect scheme is not-empty, the redirect port MUST be the well-known + port associated with the redirect scheme. Specifically "http" to port 80 + and "https" to port 443. If the redirect scheme does not have a + well-known port, the listener port of the Gateway SHOULD be used. + * If redirect scheme is empty, the redirect port MUST be the Gateway + Listener port. + + Implementations SHOULD NOT add the port number in the 'Location' + header in the following cases: + + * A Location header that will use HTTP (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 80. + * A Location header that will use HTTPS (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 443. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: |- + Scheme is the scheme to be used in the value of the `Location` header in + the response. When empty, the scheme of the request is used. + + Scheme redirects can affect the port of the redirect, for more information, + refer to the documentation for the port field of this filter. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Extended + enum: + - http + - https + type: string + statusCode: + default: 302 + description: |- + StatusCode is the HTTP status code to be used in response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Core + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP + Header name and value as defined by RFC + 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP + Header to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations must support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by + specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` should be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: |- + URLRewrite defines a schema for a filter that modifies a request during forwarding. + + Support: Extended + properties: + hostname: + description: |- + Hostname is the value to be used to replace the Host header value during + forwarding. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines a path rewrite. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified + when type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? + has(self.replaceFullPath) : true' + - message: type must be 'ReplaceFullPath' when + replaceFullPath is set + rule: 'has(self.replaceFullPath) ? self.type + == ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified + when type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' + ? has(self.replacePrefixMatch) : true' + - message: type must be 'ReplacePrefixMatch' + when replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil + if the filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type + != ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type + == ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil + if the filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type + != ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for + RequestMirror filter.type + rule: '!(!has(self.requestMirror) && self.type == + ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the + filter.type is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != + ''RequestRedirect'')' + - message: filter.requestRedirect must be specified + for RequestRedirect filter.type + rule: '!(!has(self.requestRedirect) && self.type == + ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type + is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite + filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for + ExtensionRef filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') + && self.exists(f, f.type == ''URLRewrite''))' + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') + && self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() + <= 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() + <= 1 + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + weight: + default: 1 + description: |- + Weight specifies the proportion of requests forwarded to the referenced + backend. This is computed as weight/(sum of all weights in this + BackendRefs list). For non-zero values, there may be some epsilon from + the exact proportion defined here depending on the precision an + implementation supports. Weight is not a percentage and the sum of + weights does not need to equal 100. + + If only one backend is specified and it has a weight greater than 0, 100% + of the traffic is forwarded to that backend. If weight is set to 0, no + traffic should be forwarded for this entry. If unspecified, weight + defaults to 1. + + Support for this field varies based on the context where used. + format: int32 + maximum: 1000000 + minimum: 0 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + maxItems: 16 + type: array + filters: + description: |- + Filters define the filters that are applied to requests that match + this rule. + + Wherever possible, implementations SHOULD implement filters in the order + they are specified. + + Implementations MAY choose to implement this ordering strictly, rejecting + any combination or order of filters that cannot be supported. If implementations + choose a strict interpretation of filter ordering, they MUST clearly document + that behavior. + + To reject an invalid combination or order of filters, implementations SHOULD + consider the Route Rules with this configuration invalid. If all Route Rules + in a Route are invalid, the entire Route would be considered invalid. If only + a portion of Route Rules are invalid, implementations MUST set the + "PartiallyInvalid" condition for the Route. + + Conformance-levels at this level are defined based on the type of filter: + + - ALL core filters MUST be supported by all implementations. + - Implementers are encouraged to support extended filters. + - Implementation-specific custom filters have no API guarantees across + implementations. + + Specifying the same filter multiple times is not supported unless explicitly + indicated in the filter. + + All filters are expected to be compatible with each other except for the + URLRewrite and RequestRedirect filters, which may not be combined. If an + implementation cannot support other combinations of filters, they must clearly + document that limitation. In cases where incompatible or unsupported + filters are specified and cause the `Accepted` condition to be set to status + `False`, implementations may use the `IncompatibleFilters` reason to specify + this configuration error. + + Support: Core + items: + description: |- + HTTPRouteFilter defines processing steps that must be completed during the + request or response lifecycle. HTTPRouteFilters are meant as an extension + point to express processing that may be done in Gateway implementations. Some + examples include request or response modification, implementing + authentication strategies, rate-limiting, and traffic shaping. API + guarantee/conformance is defined based on the type of the filter. + properties: + extensionRef: + description: |- + ExtensionRef is an optional, implementation-specific extension to the + "filter" behavior. For example, resource "myroutefilter" in group + "networking.example.net"). ExtensionRef MUST NOT be used for core and + extended filters. + + This filter can be used multiple times within the same rule. + + Support: Implementation-specific + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For example + "HTTPRoute" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + requestHeaderModifier: + description: |- + RequestHeaderModifier defines a schema for a filter that modifies request + headers. + + Support: Core + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + requestMirror: + description: |- + RequestMirror defines a schema for a filter that mirrors requests. + Requests are sent to the specified destination, but responses from + that destination are ignored. + + This filter can be used multiple times within the same rule. Note that + not all implementations will be able to support mirroring to multiple + backends. + + Support: Extended + properties: + backendRef: + description: |- + BackendRef references a resource where mirrored requests are sent. + + Mirrored requests must be sent only to a single destination endpoint + within this BackendRef, irrespective of how many endpoints are present + within this BackendRef. + + If the referent cannot be found, this BackendRef is invalid and must be + dropped from the Gateway. The controller must ensure the "ResolvedRefs" + condition on the Route status is set to `status: False` and not configure + this backend in the underlying implementation. + + If there is a cross-namespace reference to an *existing* object + that is not allowed by a ReferenceGrant, the controller must ensure the + "ResolvedRefs" condition on the Route is set to `status: False`, + with the "RefNotPermitted" reason and not configure this backend in the + underlying implementation. + + In either error case, the Message of the `ResolvedRefs` Condition + should be used to provide more detail about the problem. + + Support: Extended for Kubernetes Service + + Support: Implementation-specific for any other resource + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Service + description: |- + Kind is the Kubernetes resource kind of the referent. For example + "Service". + + Defaults to "Service" when not specified. + + ExternalName services can refer to CNAME DNS records that may live + outside of the cluster and as such are difficult to reason about in + terms of conformance. They also may not be safe to forward to (see + CVE-2021-25740 for more information). Implementations SHOULD NOT + support ExternalName Services. + + Support: Core (Services with a type other than ExternalName) + + Support: Implementation-specific (Services with type ExternalName) + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the backend. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port specifies the destination port number to use for this resource. + Port is required when the referent is a Kubernetes Service. In this + case, the port number is the service port number, not the target port. + For other resources, destination port might be derived from the referent + resource or this field. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - name + type: object + x-kubernetes-validations: + - message: Must have port for Service reference + rule: '(size(self.group) == 0 && self.kind == ''Service'') + ? has(self.port) : true' + fraction: + description: |- + Fraction represents the fraction of requests that should be + mirrored to BackendRef. + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + properties: + denominator: + default: 100 + format: int32 + minimum: 1 + type: integer + numerator: + format: int32 + minimum: 0 + type: integer + required: + - numerator + type: object + x-kubernetes-validations: + - message: numerator must be less than or equal to + denominator + rule: self.numerator <= self.denominator + percent: + description: |- + Percent represents the percentage of requests that should be + mirrored to BackendRef. Its minimum value is 0 (indicating 0% of + requests) and its maximum value is 100 (indicating 100% of requests). + + Only one of Fraction or Percent may be specified. If neither field + is specified, 100% of requests will be mirrored. + format: int32 + maximum: 100 + minimum: 0 + type: integer + required: + - backendRef + type: object + x-kubernetes-validations: + - message: Only one of percent or fraction may be specified + in HTTPRequestMirrorFilter + rule: '!(has(self.percent) && has(self.fraction))' + requestRedirect: + description: |- + RequestRedirect defines a schema for a filter that responds to the + request with an HTTP redirection. + + Support: Core + properties: + hostname: + description: |- + Hostname is the hostname to be used in the value of the `Location` + header in the response. + When empty, the hostname in the `Host` header of the request is used. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines parameters used to modify the path of the incoming request. + The modified path is then used to construct the `Location` header. When + empty, the request path is used as-is. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when + type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) + : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath + is set + rule: 'has(self.replaceFullPath) ? self.type == + ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when + type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) + : true' + - message: type must be 'ReplacePrefixMatch' when + replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + port: + description: |- + Port is the port to be used in the value of the `Location` + header in the response. + + If no port is specified, the redirect port MUST be derived using the + following rules: + + * If redirect scheme is not-empty, the redirect port MUST be the well-known + port associated with the redirect scheme. Specifically "http" to port 80 + and "https" to port 443. If the redirect scheme does not have a + well-known port, the listener port of the Gateway SHOULD be used. + * If redirect scheme is empty, the redirect port MUST be the Gateway + Listener port. + + Implementations SHOULD NOT add the port number in the 'Location' + header in the following cases: + + * A Location header that will use HTTP (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 80. + * A Location header that will use HTTPS (whether that is determined via + the Listener protocol or the Scheme field) _and_ use port 443. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + scheme: + description: |- + Scheme is the scheme to be used in the value of the `Location` header in + the response. When empty, the scheme of the request is used. + + Scheme redirects can affect the port of the redirect, for more information, + refer to the documentation for the port field of this filter. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Extended + enum: + - http + - https + type: string + statusCode: + default: 302 + description: |- + StatusCode is the HTTP status code to be used in response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + + Support: Core + enum: + - 301 + - 302 + type: integer + type: object + responseHeaderModifier: + description: |- + ResponseHeaderModifier defines a schema for a filter that modifies response + headers. + + Support: Extended + properties: + add: + description: |- + Add adds the given header(s) (name, value) to the request + before the action. It appends to any existing values associated + with the header name. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + add: + - name: "my-header" + value: "bar,baz" + + Output: + GET /foo HTTP/1.1 + my-header: foo,bar,baz + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + remove: + description: |- + Remove the given header(s) from the HTTP request before the action. The + value of Remove is a list of HTTP header names. Note that the header + names are case-insensitive (see + https://datatracker.ietf.org/doc/html/rfc2616#section-4.2). + + Input: + GET /foo HTTP/1.1 + my-header1: foo + my-header2: bar + my-header3: baz + + Config: + remove: ["my-header1", "my-header3"] + + Output: + GET /foo HTTP/1.1 + my-header2: bar + items: + type: string + maxItems: 16 + type: array + x-kubernetes-list-type: set + set: + description: |- + Set overwrites the request with the given header (name, value) + before the action. + + Input: + GET /foo HTTP/1.1 + my-header: foo + + Config: + set: + - name: "my-header" + value: "bar" + + Output: + GET /foo HTTP/1.1 + my-header: bar + items: + description: HTTPHeader represents an HTTP Header + name and value as defined by RFC 7230. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, the first entry with + an equivalent name MUST be considered for a match. Subsequent entries + with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + value: + description: Value is the value of HTTP Header + to be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + type: + description: |- + Type identifies the type of filter to apply. As with other API fields, + types are classified into three conformance levels: + + - Core: Filter types and their corresponding configuration defined by + "Support: Core" in this package, e.g. "RequestHeaderModifier". All + implementations must support core filters. + + - Extended: Filter types and their corresponding configuration defined by + "Support: Extended" in this package, e.g. "RequestMirror". Implementers + are encouraged to support extended filters. + + - Implementation-specific: Filters that are defined and supported by + specific vendors. + In the future, filters showing convergence in behavior across multiple + implementations will be considered for inclusion in extended or core + conformance levels. Filter-specific configuration for such filters + is specified using the ExtensionRef field. `Type` should be set to + "ExtensionRef" for custom filters. + + Implementers are encouraged to define custom implementation types to + extend the core API with implementation-specific behavior. + + If a reference to a custom filter type cannot be resolved, the filter + MUST NOT be skipped. Instead, requests that would have been processed by + that filter MUST receive a HTTP error response. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - RequestHeaderModifier + - ResponseHeaderModifier + - RequestMirror + - RequestRedirect + - URLRewrite + - ExtensionRef + type: string + urlRewrite: + description: |- + URLRewrite defines a schema for a filter that modifies a request during forwarding. + + Support: Extended + properties: + hostname: + description: |- + Hostname is the value to be used to replace the Host header value during + forwarding. + + Support: Extended + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + path: + description: |- + Path defines a path rewrite. + + Support: Extended + properties: + replaceFullPath: + description: |- + ReplaceFullPath specifies the value with which to replace the full path + of a request during a rewrite or redirect. + maxLength: 1024 + type: string + replacePrefixMatch: + description: |- + ReplacePrefixMatch specifies the value with which to replace the prefix + match of a request during a rewrite or redirect. For example, a request + to "/foo/bar" with a prefix match of "/foo" and a ReplacePrefixMatch + of "/xyz" would be modified to "/xyz/bar". + + Note that this matches the behavior of the PathPrefix match type. This + matches full path elements. A path element refers to the list of labels + in the path split by the `/` separator. When specified, a trailing `/` is + ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all + match the prefix `/abc`, but the path `/abcd` would not. + + ReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. + Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in + the implementation setting the Accepted Condition for the Route to `status: False`. + + Request Path | Prefix Match | Replace Prefix | Modified Path + maxLength: 1024 + type: string + type: + description: |- + Type defines the type of path modifier. Additional types may be + added in a future release of the API. + + Note that values may be added to this enum, implementations + must ensure that unknown values will not cause a crash. + + Unknown values here must result in the implementation setting the + Accepted Condition for the Route to `status: False`, with a + Reason of `UnsupportedValue`. + enum: + - ReplaceFullPath + - ReplacePrefixMatch + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: replaceFullPath must be specified when + type is set to 'ReplaceFullPath' + rule: 'self.type == ''ReplaceFullPath'' ? has(self.replaceFullPath) + : true' + - message: type must be 'ReplaceFullPath' when replaceFullPath + is set + rule: 'has(self.replaceFullPath) ? self.type == + ''ReplaceFullPath'' : true' + - message: replacePrefixMatch must be specified when + type is set to 'ReplacePrefixMatch' + rule: 'self.type == ''ReplacePrefixMatch'' ? has(self.replacePrefixMatch) + : true' + - message: type must be 'ReplacePrefixMatch' when + replacePrefixMatch is set + rule: 'has(self.replacePrefixMatch) ? self.type + == ''ReplacePrefixMatch'' : true' + type: object + required: + - type + type: object + x-kubernetes-validations: + - message: filter.requestHeaderModifier must be nil if the + filter.type is not RequestHeaderModifier + rule: '!(has(self.requestHeaderModifier) && self.type != + ''RequestHeaderModifier'')' + - message: filter.requestHeaderModifier must be specified + for RequestHeaderModifier filter.type + rule: '!(!has(self.requestHeaderModifier) && self.type == + ''RequestHeaderModifier'')' + - message: filter.responseHeaderModifier must be nil if the + filter.type is not ResponseHeaderModifier + rule: '!(has(self.responseHeaderModifier) && self.type != + ''ResponseHeaderModifier'')' + - message: filter.responseHeaderModifier must be specified + for ResponseHeaderModifier filter.type + rule: '!(!has(self.responseHeaderModifier) && self.type + == ''ResponseHeaderModifier'')' + - message: filter.requestMirror must be nil if the filter.type + is not RequestMirror + rule: '!(has(self.requestMirror) && self.type != ''RequestMirror'')' + - message: filter.requestMirror must be specified for RequestMirror + filter.type + rule: '!(!has(self.requestMirror) && self.type == ''RequestMirror'')' + - message: filter.requestRedirect must be nil if the filter.type + is not RequestRedirect + rule: '!(has(self.requestRedirect) && self.type != ''RequestRedirect'')' + - message: filter.requestRedirect must be specified for RequestRedirect + filter.type + rule: '!(!has(self.requestRedirect) && self.type == ''RequestRedirect'')' + - message: filter.urlRewrite must be nil if the filter.type + is not URLRewrite + rule: '!(has(self.urlRewrite) && self.type != ''URLRewrite'')' + - message: filter.urlRewrite must be specified for URLRewrite + filter.type + rule: '!(!has(self.urlRewrite) && self.type == ''URLRewrite'')' + - message: filter.extensionRef must be nil if the filter.type + is not ExtensionRef + rule: '!(has(self.extensionRef) && self.type != ''ExtensionRef'')' + - message: filter.extensionRef must be specified for ExtensionRef + filter.type + rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: May specify either httpRouteFilterRequestRedirect + or httpRouteFilterRequestRewrite, but not both + rule: '!(self.exists(f, f.type == ''RequestRedirect'') && + self.exists(f, f.type == ''URLRewrite''))' + - message: RequestHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'RequestHeaderModifier').size() + <= 1 + - message: ResponseHeaderModifier filter cannot be repeated + rule: self.filter(f, f.type == 'ResponseHeaderModifier').size() + <= 1 + - message: RequestRedirect filter cannot be repeated + rule: self.filter(f, f.type == 'RequestRedirect').size() <= + 1 + - message: URLRewrite filter cannot be repeated + rule: self.filter(f, f.type == 'URLRewrite').size() <= 1 + matches: + default: + - path: + type: PathPrefix + value: / + description: |- + Matches define conditions used for matching the rule against incoming + HTTP requests. Each match is independent, i.e. this rule will be matched + if **any** one of the matches is satisfied. + + For example, take the following matches configuration: + + ``` + matches: + - path: + value: "/foo" + headers: + - name: "version" + value: "v2" + - path: + value: "/v2/foo" + ``` + + For a request to match against this rule, a request must satisfy + EITHER of the two conditions: + + - path prefixed with `/foo` AND contains the header `version: v2` + - path prefix of `/v2/foo` + + See the documentation for HTTPRouteMatch on how to specify multiple + match conditions that should be ANDed together. + + If no matches are specified, the default is a prefix + path match on "/", which has the effect of matching every + HTTP request. + + Proxy or Load Balancer routing configuration generated from HTTPRoutes + MUST prioritize matches based on the following criteria, continuing on + ties. Across all rules specified on applicable Routes, precedence must be + given to the match having: + + * "Exact" path match. + * "Prefix" path match with largest number of characters. + * Method match. + * Largest number of header matches. + * Largest number of query param matches. + + Note: The precedence of RegularExpression path matches are implementation-specific. + + If ties still exist across multiple Routes, matching precedence MUST be + determined in order of the following criteria, continuing on ties: + + * The oldest Route based on creation timestamp. + * The Route appearing first in alphabetical order by + "{namespace}/{name}". + + If ties still exist within an HTTPRoute, matching precedence MUST be granted + to the FIRST matching rule (in list order) with a match meeting the above + criteria. + + When no rules matching a request have been successfully attached to the + parent a request is coming from, a HTTP 404 status code MUST be returned. + items: + description: "HTTPRouteMatch defines the predicate used to + match requests to a given\naction. Multiple match types + are ANDed together, i.e. the match will\nevaluate to true + only if all conditions are satisfied.\n\nFor example, the + match below will match a HTTP request only if its path\nstarts + with `/foo` AND it contains the `version: v1` header:\n\n```\nmatch:\n\n\tpath:\n\t + \ value: \"/foo\"\n\theaders:\n\t- name: \"version\"\n\t + \ value \"v1\"\n\n```" + properties: + headers: + description: |- + Headers specifies HTTP request header matchers. Multiple match values are + ANDed together, meaning, a request must match all the specified headers + to select the route. + items: + description: |- + HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request + headers. + properties: + name: + description: |- + Name is the name of the HTTP Header to be matched. Name matching MUST be + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + + If multiple entries specify equivalent header names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent header name MUST be ignored. Due to the + case-insensitivity of header names, "foo" and "Foo" are considered + equivalent. + + When a header is repeated in an HTTP request, it is + implementation-specific behavior as to how this is represented. + Generally, proxies should follow the guidance from the RFC: + https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding + processing a repeated header, with special handling for "Set-Cookie". + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the header. + + Support: Core (Exact) + + Support: Implementation-specific (RegularExpression) + + Since RegularExpression HeaderMatchType has implementation-specific + conformance, implementations can support POSIX, PCRE or any other dialects + of regular expressions. Please read the implementation's documentation to + determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP Header to + be matched. + maxLength: 4096 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + method: + description: |- + Method specifies HTTP method matcher. + When specified, this route will be matched only if the request has the + specified method. + + Support: Extended + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + type: string + path: + default: + type: PathPrefix + value: / + description: |- + Path specifies a HTTP request path matcher. If this field is not + specified, a default prefix match on the "/" path is provided. + properties: + type: + default: PathPrefix + description: |- + Type specifies how to match against the path Value. + + Support: Core (Exact, PathPrefix) + + Support: Implementation-specific (RegularExpression) + enum: + - Exact + - PathPrefix + - RegularExpression + type: string + value: + default: / + description: Value of the HTTP path to match against. + maxLength: 1024 + type: string + type: object + x-kubernetes-validations: + - message: value must be an absolute path and start with + '/' when type one of ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.startsWith(''/'') + : true' + - message: must not contain '//' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''//'') + : true' + - message: must not contain '/./' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/./'') + : true' + - message: must not contain '/../' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''/../'') + : true' + - message: must not contain '%2f' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2f'') + : true' + - message: must not contain '%2F' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''%2F'') + : true' + - message: must not contain '#' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.contains(''#'') + : true' + - message: must not end with '/..' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/..'') + : true' + - message: must not end with '/.' when type one of ['Exact', + 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? !self.value.endsWith(''/.'') + : true' + - message: type must be one of ['Exact', 'PathPrefix', + 'RegularExpression'] + rule: self.type in ['Exact','PathPrefix'] || self.type + == 'RegularExpression' + - message: must only contain valid characters (matching + ^(?:[-A-Za-z0-9/._~!$&'()*+,;=:@]|[%][0-9a-fA-F]{2})+$) + for types ['Exact', 'PathPrefix'] + rule: '(self.type in [''Exact'',''PathPrefix'']) ? self.value.matches(r"""^(?:[-A-Za-z0-9/._~!$&''()*+,;=:@]|[%][0-9a-fA-F]{2})+$""") + : true' + queryParams: + description: |- + QueryParams specifies HTTP query parameter matchers. Multiple match + values are ANDed together, meaning, a request must match all the + specified query parameters to select the route. + + Support: Extended + items: + description: |- + HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP + query parameters. + properties: + name: + description: |- + Name is the name of the HTTP query param to be matched. This must be an + exact string match. (See + https://tools.ietf.org/html/rfc7230#section-2.7.3). + + If multiple entries specify equivalent query param names, only the first + entry with an equivalent name MUST be considered for a match. Subsequent + entries with an equivalent query param name MUST be ignored. + + If a query param is repeated in an HTTP request, the behavior is + purposely left undefined, since different data planes have different + capabilities. However, it is *recommended* that implementations should + match against the first value of the param if the data plane supports it, + as this behavior is expected in other load balancing contexts outside of + the Gateway API. + + Users SHOULD NOT route traffic based on repeated query params to guard + themselves against potential differences in the implementations. + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + type: + default: Exact + description: |- + Type specifies how to match against the value of the query parameter. + + Support: Extended (Exact) + + Support: Implementation-specific (RegularExpression) + + Since RegularExpression QueryParamMatchType has Implementation-specific + conformance, implementations can support POSIX, PCRE or any other + dialects of regular expressions. Please read the implementation's + documentation to determine the supported dialect. + enum: + - Exact + - RegularExpression + type: string + value: + description: Value is the value of HTTP query param + to be matched. + maxLength: 1024 + minLength: 1 + type: string + required: + - name + - value + type: object + maxItems: 16 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + maxItems: 64 + type: array + timeouts: + description: |- + Timeouts defines the timeouts that can be configured for an HTTP request. + + Support: Extended + properties: + backendRequest: + description: |- + BackendRequest specifies a timeout for an individual request from the gateway + to a backend. This covers the time from when the request first starts being + sent from the gateway to when the full response has been received from the backend. + + Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout + completely. Implementations that cannot completely disable the timeout MUST + instead interpret the zero duration as the longest possible value to which + the timeout can be set. + + An entire client HTTP transaction with a gateway, covered by the Request timeout, + may result in more than one call from the gateway to the destination backend, + for example, if automatic retries are supported. + + The value of BackendRequest must be a Gateway API Duration string as defined by + GEP-2257. When this field is unspecified, its behavior is implementation-specific; + when specified, the value of BackendRequest must be no more than the value of the + Request timeout (since the Request timeout encompasses the BackendRequest timeout). + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + request: + description: |- + Request specifies the maximum duration for a gateway to respond to an HTTP request. + If the gateway has not been able to respond before this deadline is met, the gateway + MUST return a timeout error. + + For example, setting the `rules.timeouts.request` field to the value `10s` in an + `HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds + to complete. + + Setting a timeout to the zero duration (e.g. "0s") SHOULD disable the timeout + completely. Implementations that cannot completely disable the timeout MUST + instead interpret the zero duration as the longest possible value to which + the timeout can be set. + + This timeout is intended to cover as close to the whole request-response transaction + as possible although an implementation MAY choose to start the timeout after the entire + request stream has been received instead of immediately after the transaction is + initiated by the client. + + The value of Request is a Gateway API Duration string as defined by GEP-2257. When this + field is unspecified, request timeout behavior is implementation-specific. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + type: object + x-kubernetes-validations: + - message: backendRequest timeout cannot be longer than request + timeout + rule: '!(has(self.request) && has(self.backendRequest) && + duration(self.request) != duration(''0s'') && duration(self.backendRequest) + > duration(self.request))' + type: object + x-kubernetes-validations: + - message: RequestRedirect filter must not be used together with + backendRefs + rule: '(has(self.backendRefs) && size(self.backendRefs) > 0) ? + (!has(self.filters) || self.filters.all(f, !has(f.requestRedirect))): + true' + - message: When using RequestRedirect filter with path.replacePrefixMatch, + exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.requestRedirect) + && has(f.requestRedirect.path) && f.requestRedirect.path.type + == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) + ? ((size(self.matches) != 1 || !has(self.matches[0].path) || + self.matches[0].path.type != ''PathPrefix'') ? false : true) + : true' + - message: When using URLRewrite filter with path.replacePrefixMatch, + exactly one PathPrefix match must be specified + rule: '(has(self.filters) && self.filters.exists_one(f, has(f.urlRewrite) + && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' + && has(f.urlRewrite.path.replacePrefixMatch))) ? ((size(self.matches) + != 1 || !has(self.matches[0].path) || self.matches[0].path.type + != ''PathPrefix'') ? false : true) : true' + - message: Within backendRefs, when using RequestRedirect filter + with path.replacePrefixMatch, exactly one PathPrefix match must + be specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, + (has(b.filters) && b.filters.exists_one(f, has(f.requestRedirect) + && has(f.requestRedirect.path) && f.requestRedirect.path.type + == ''ReplacePrefixMatch'' && has(f.requestRedirect.path.replacePrefixMatch))) + )) ? ((size(self.matches) != 1 || !has(self.matches[0].path) + || self.matches[0].path.type != ''PathPrefix'') ? false : true) + : true' + - message: Within backendRefs, When using URLRewrite filter with + path.replacePrefixMatch, exactly one PathPrefix match must be + specified + rule: '(has(self.backendRefs) && self.backendRefs.exists_one(b, + (has(b.filters) && b.filters.exists_one(f, has(f.urlRewrite) + && has(f.urlRewrite.path) && f.urlRewrite.path.type == ''ReplacePrefixMatch'' + && has(f.urlRewrite.path.replacePrefixMatch))) )) ? ((size(self.matches) + != 1 || !has(self.matches[0].path) || self.matches[0].path.type + != ''PathPrefix'') ? false : true) : true' + maxItems: 16 + type: array + x-kubernetes-validations: + - message: While 16 rules and 64 matches per rule are allowed, the + total number of matches across all rules in a route must be less + than 128 + rule: '(self.size() > 0 ? self[0].matches.size() : 0) + (self.size() + > 1 ? self[1].matches.size() : 0) + (self.size() > 2 ? self[2].matches.size() + : 0) + (self.size() > 3 ? self[3].matches.size() : 0) + (self.size() + > 4 ? self[4].matches.size() : 0) + (self.size() > 5 ? self[5].matches.size() + : 0) + (self.size() > 6 ? self[6].matches.size() : 0) + (self.size() + > 7 ? self[7].matches.size() : 0) + (self.size() > 8 ? self[8].matches.size() + : 0) + (self.size() > 9 ? self[9].matches.size() : 0) + (self.size() + > 10 ? self[10].matches.size() : 0) + (self.size() > 11 ? self[11].matches.size() + : 0) + (self.size() > 12 ? self[12].matches.size() : 0) + (self.size() + > 13 ? self[13].matches.size() : 0) + (self.size() > 14 ? self[14].matches.size() + : 0) + (self.size() > 15 ? self[15].matches.size() : 0) <= 128' + type: object + status: + description: Status defines the current state of HTTPRoute. + properties: + parents: + description: |- + Parents is a list of parent resources (usually Gateways) that are + associated with the route, and the status of the route with respect to + each parent. When this route attaches to a parent, the controller that + manages the parent must add an entry to this list when the controller + first sees the route and should update the entry as appropriate when the + route or gateway is modified. + + Note that parent references that cannot be resolved by an implementation + of this API will not be added to this list. Implementations of this API + can only populate Route status for the Gateways/parent resources they are + responsible for. + + A maximum of 32 Gateways will be represented in this list. An empty list + means the route has not been attached to any Gateway. + items: + description: |- + RouteParentStatus describes the status of a route with respect to an + associated Parent. + properties: + conditions: + description: |- + Conditions describes the status of the route with respect to the Gateway. + Note that the route's availability is also subject to the Gateway's own + status conditions and listener status. + + If the Route's ParentRef specifies an existing Gateway that supports + Routes of this kind AND that Gateway's controller has sufficient access, + then that Gateway's controller MUST set the "Accepted" condition on the + Route, to indicate whether the route has been accepted or rejected by the + Gateway, and why. + + A Route MUST be considered "Accepted" if at least one of the Route's + rules is implemented by the Gateway. + + There are a number of cases where the "Accepted" condition may not be set + due to lack of controller visibility, that includes when: + + * The Route refers to a nonexistent parent. + * The Route is of a type that the controller does not support. + * The Route is in a namespace the controller does not have access to. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + parentRef: + description: |- + ParentRef corresponds with a ParentRef in the spec that this + RouteParentStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + required: + - controllerName + - parentRef + type: object + maxItems: 32 + type: array + required: + - parents + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/standard-gatewayapi-crds.yaml +# +# config/crd/standard/gateway.networking.k8s.io_referencegrants.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: standard + creationTimestamp: null + name: referencegrants.gateway.networking.k8s.io +spec: + group: gateway.networking.k8s.io + names: + categories: + - gateway-api + kind: ReferenceGrant + listKind: ReferenceGrantList + plural: referencegrants + shortNames: + - refgrant + singular: referencegrant + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: |- + ReferenceGrant identifies kinds of resources in other namespaces that are + trusted to reference the specified kinds of resources in the same namespace + as the policy. + + Each ReferenceGrant can be used to represent a unique trust relationship. + Additional Reference Grants can be used to add to the set of trusted + sources of inbound references for the namespace they are defined within. + + All cross-namespace references in Gateway API (with the exception of cross-namespace + Gateway-route attachment) require a ReferenceGrant. + + ReferenceGrant is a form of runtime verification allowing users to assert + which cross-namespace object references are permitted. Implementations that + support ReferenceGrant MUST NOT permit cross-namespace references which have + no grant, and MUST respond to the removal of a grant by revoking the access + that the grant allowed. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of ReferenceGrant. + properties: + from: + description: |- + From describes the trusted namespaces and kinds that can reference the + resources described in "To". Each entry in this list MUST be considered + to be an additional place that references can be valid from, or to put + this another way, entries MUST be combined using OR. + + Support: Core + items: + description: ReferenceGrantFrom describes trusted namespaces and + kinds. + properties: + group: + description: |- + Group is the group of the referent. + When empty, the Kubernetes core API group is inferred. + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: |- + Kind is the kind of the referent. Although implementations may support + additional resources, the following types are part of the "Core" + support level for this field. + + When used to permit a SecretObjectReference: + + * Gateway + + When used to permit a BackendObjectReference: + + * GRPCRoute + * HTTPRoute + * TCPRoute + * TLSRoute + * UDPRoute + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + namespace: + description: |- + Namespace is the namespace of the referent. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - namespace + type: object + maxItems: 16 + minItems: 1 + type: array + to: + description: |- + To describes the resources that may be referenced by the resources + described in "From". Each entry in this list MUST be considered to be an + additional place that references can be valid to, or to put this another + way, entries MUST be combined using OR. + + Support: Core + items: + description: |- + ReferenceGrantTo describes what Kinds are allowed as targets of the + references. + properties: + group: + description: |- + Group is the group of the referent. + When empty, the Kubernetes core API group is inferred. + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: |- + Kind is the kind of the referent. Although implementations may support + additional resources, the following types are part of the "Core" + support level for this field: + + * Secret when used to permit a SecretObjectReference + * Service when used to permit a BackendObjectReference + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. When unspecified, this policy + refers to all resources of the specified Group and Kind in the local + namespace. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + type: object + maxItems: 16 + minItems: 1 + type: array + required: + - from + - to + type: object + type: object + served: true + storage: true + subresources: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# Source: gateway-crds-helm/templates/standard-gatewayapi-crds.yaml +# Copyright 2025 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Gateway API Standard channel install +# diff --git a/tools/make/kube.mk b/tools/make/kube.mk index 020e4da744..cdd2b1f7f4 100644 --- a/tools/make/kube.mk +++ b/tools/make/kube.mk @@ -10,7 +10,9 @@ ENVTEST_K8S_VERSIONS ?= 1.29.4 1.30.3 1.31.0 1.32.0 # For more details, see https://gateway-api.sigs.k8s.io/guides/getting-started/#installing-gateway-api GATEWAY_API_VERSION ?= $(shell go list -m -f '{{.Version}}' sigs.k8s.io/gateway-api) -GATEWAY_RELEASE_URL ?= https://github.com/kubernetes-sigs/gateway-api/releases/download/${GATEWAY_API_VERSION}/experimental-install.yaml +GATEWAY_API_RELEASE_URL ?= https://github.com/kubernetes-sigs/gateway-api/releases/download/${GATEWAY_API_VERSION} +EXPERIMENTAL_GATEWAY_API_RELEASE_URL ?= ${GATEWAY_API_RELEASE_URL}/experimental-install.yaml +STANDARD_GATEWAY_API_RELEASE_URL ?= ${GATEWAY_API_RELEASE_URL}/standard-install.yaml WAIT_TIMEOUT ?= 15m @@ -82,12 +84,17 @@ generate-gwapi-manifests: ## Generate GWAPI manifests and make it consistent wit @$(LOG_TARGET) @echo "Generating Gateway API CRDs" @mkdir -p $(OUTPUT_DIR)/ - @curl -sLo $(OUTPUT_DIR)/gatewayapi-crds.yaml ${GATEWAY_RELEASE_URL} - cp $(OUTPUT_DIR)/gatewayapi-crds.yaml charts/gateway-helm/crds/gatewayapi-crds.yaml - @sed -i.bak '1s/^/{{- if .Values.crds.gatewayAPI.enabled }}\n/' $(OUTPUT_DIR)/gatewayapi-crds.yaml && \ - echo '{{- end }}' >> $(OUTPUT_DIR)/gatewayapi-crds.yaml && \ - rm -f $(OUTPUT_DIR)/gatewayapi-crds.yaml.bak - @mv $(OUTPUT_DIR)/gatewayapi-crds.yaml charts/gateway-crds-helm/templates/gatewayapi-crds.yaml + @curl -sLo $(OUTPUT_DIR)/experimental-gatewayapi-crds.yaml ${EXPERIMENTAL_GATEWAY_API_RELEASE_URL} + @curl -sLo $(OUTPUT_DIR)/standard-gatewayapi-crds.yaml ${STANDARD_GATEWAY_API_RELEASE_URL} + cp $(OUTPUT_DIR)/experimental-gatewayapi-crds.yaml charts/gateway-helm/crds/gatewayapi-crds.yaml + @sed -i.bak '1s/^/{{- if and .Values.crds.gatewayAPI.enabled (eq .Values.crds.gatewayAPI.channel "standard") }}\n/' $(OUTPUT_DIR)/standard-gatewayapi-crds.yaml && \ + echo '{{- end }}' >> $(OUTPUT_DIR)/standard-gatewayapi-crds.yaml && \ + sed -i.bak '1s/^/{{- if and .Values.crds.gatewayAPI.enabled (or (eq .Values.crds.gatewayAPI.channel "experimental") (eq .Values.crds.gatewayAPI.channel "")) }}\n/' $(OUTPUT_DIR)/experimental-gatewayapi-crds.yaml && \ + echo '{{- end }}' >> $(OUTPUT_DIR)/experimental-gatewayapi-crds.yaml && \ + rm -f $(OUTPUT_DIR)/standard-gatewayapi-crds.yaml.bak && \ + rm -f $(OUTPUT_DIR)/experimental-gatewayapi-crds.yaml.bak + @mv $(OUTPUT_DIR)/experimental-gatewayapi-crds.yaml charts/gateway-crds-helm/templates/experimental-gatewayapi-crds.yaml + @mv $(OUTPUT_DIR)/standard-gatewayapi-crds.yaml charts/gateway-crds-helm/templates/standard-gatewayapi-crds.yaml .PHONY: kube-generate kube-generate: ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. From c3f4750ceb412d58f6589b8d169af746b9353501 Mon Sep 17 00:00:00 2001 From: zirain Date: Sat, 10 May 2025 01:47:17 +0800 Subject: [PATCH 41/66] e2e: bump upgrade test version to v1.3.2 (#5976) e2e: bump upgrade test version Signed-off-by: zirain Signed-off-by: Arko Dasgupta --- test/e2e/tests/eg_upgrade.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/tests/eg_upgrade.go b/test/e2e/tests/eg_upgrade.go index 3418259943..f1d99b1509 100644 --- a/test/e2e/tests/eg_upgrade.go +++ b/test/e2e/tests/eg_upgrade.go @@ -52,7 +52,7 @@ var EGUpgradeTest = suite.ConformanceTest{ chartPath := "../../../charts/gateway-helm" relName := "eg" depNS := "envoy-gateway-system" - lastVersionTag := "v1.2.3" // the latest prior release + lastVersionTag := "v1.3.2" // the latest prior release t.Logf("Upgrading from version: %s", lastVersionTag) From 3ae9fe29f9ea11d7005b8ad65eba02317b78d2c2 Mon Sep 17 00:00:00 2001 From: Gavin Lam Date: Fri, 9 May 2025 13:55:45 -0400 Subject: [PATCH 42/66] fix: add validation for duplicated API keys (#5955) * reject duplicated API keys * enhance api-key-auth e2e test to cover duplicated client IDs Signed-off-by: Gavin Lam Signed-off-by: Arko Dasgupta --- internal/gatewayapi/securitypolicy.go | 9 + ...th-api-key-auth-duplicated-api-key.in.yaml | 62 ++++++ ...h-api-key-auth-duplicated-api-key.out.yaml | 183 ++++++++++++++++++ release-notes/current.yaml | 1 + test/e2e/testdata/api-key-auth.yaml | 12 ++ 5 files changed, 267 insertions(+) create mode 100644 internal/gatewayapi/testdata/httproute-with-api-key-auth-duplicated-api-key.in.yaml create mode 100644 internal/gatewayapi/testdata/httproute-with-api-key-auth-duplicated-api-key.out.yaml diff --git a/internal/gatewayapi/securitypolicy.go b/internal/gatewayapi/securitypolicy.go index 3bc82d8907..e2dd18b2b1 100644 --- a/internal/gatewayapi/securitypolicy.go +++ b/internal/gatewayapi/securitypolicy.go @@ -1124,6 +1124,8 @@ func (t *Translator) buildAPIKeyAuth( } credentials := make(map[string]ir.PrivateBytes) + seenKeys := make(sets.Set[string]) + for _, ref := range policy.Spec.APIKeyAuth.CredentialRefs { credentialsSecret, err := t.validateSecretRef( false, from, ref, resources) @@ -1134,6 +1136,13 @@ func (t *Translator) buildAPIKeyAuth( if _, ok := credentials[clientid]; ok { continue } + + keyString := string(key) + if seenKeys.Has(keyString) { + return nil, errors.New("duplicated API key") + } + + seenKeys.Insert(keyString) credentials[clientid] = key } } diff --git a/internal/gatewayapi/testdata/httproute-with-api-key-auth-duplicated-api-key.in.yaml b/internal/gatewayapi/testdata/httproute-with-api-key-auth-duplicated-api-key.in.yaml new file mode 100644 index 0000000000..615458db4d --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-api-key-auth-duplicated-api-key.in.yaml @@ -0,0 +1,62 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + hostname: "*.envoyproxy.io" + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 +securityPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + namespace: default + name: policy-for-route-1 + spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + apiKeyAuth: + extractFrom: + - headers: ["X-API-KEY"] + credentialRefs: + - name: "credential-1" +secrets: +- apiVersion: v1 + kind: Secret + metadata: + namespace: default + name: credential-1 + data: + client1: "a2V5MQ==" + client2: "a2V5Mg==" + client3: "a2V5Mg==" diff --git a/internal/gatewayapi/testdata/httproute-with-api-key-auth-duplicated-api-key.out.yaml b/internal/gatewayapi/testdata/httproute-with-api-key-auth-duplicated-api-key.out.yaml new file mode 100644 index 0000000000..6abda8770c --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-api-key-auth-duplicated-api-key.out.yaml @@ -0,0 +1,183 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + hostname: '*.envoyproxy.io' + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system +securityPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + creationTimestamp: null + name: policy-for-route-1 + namespace: default + spec: + apiKeyAuth: + credentialRefs: + - group: null + kind: null + name: credential-1 + extractFrom: + - headers: + - X-API-KEY + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: 'APIKeyAuth: duplicated API key.' + reason: Invalid + status: "False" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*.envoyproxy.io' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + directResponse: + statusCode: 500 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + security: {} + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/release-notes/current.yaml b/release-notes/current.yaml index bcced8623e..c80f6aad5b 100644 --- a/release-notes/current.yaml +++ b/release-notes/current.yaml @@ -13,6 +13,7 @@ new features: | bug fixes: | Fix reference grant from SecurityPolicy to referenced remoteJWKS backend not respected. Added validation for header values. + Added validation for duplicated API keys. # Enhancements that improve performance. performance improvements: | diff --git a/test/e2e/testdata/api-key-auth.yaml b/test/e2e/testdata/api-key-auth.yaml index ce7d99c0b7..34b75980df 100644 --- a/test/e2e/testdata/api-key-auth.yaml +++ b/test/e2e/testdata/api-key-auth.yaml @@ -10,6 +10,15 @@ data: # key2 client2: "a2V5Mg==" --- +apiVersion: v1 +kind: Secret +metadata: + namespace: gateway-conformance-infra + name: api-key-auth-users-secret-2 +data: + # key2 - duplicate client id should be ignored + client1: "a2V5Mg==" +--- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: @@ -76,6 +85,7 @@ spec: - headers: ["X-API-KEY"] credentialRefs: - name: "api-key-auth-users-secret-1" + - name: "api-key-auth-users-secret-2" --- apiVersion: gateway.envoyproxy.io/v1alpha1 kind: SecurityPolicy @@ -92,6 +102,7 @@ spec: - cookies: ["X-API-KEY"] credentialRefs: - name: "api-key-auth-users-secret-1" + - name: "api-key-auth-users-secret-2" --- apiVersion: gateway.envoyproxy.io/v1alpha1 @@ -109,3 +120,4 @@ spec: - params: ["X-API-KEY"] credentialRefs: - name: "api-key-auth-users-secret-1" + - name: "api-key-auth-users-secret-2" From dd6e608cd49886614a5afa6e600159cf5211ca0c Mon Sep 17 00:00:00 2001 From: Guy Daich Date: Fri, 9 May 2025 15:25:47 -0500 Subject: [PATCH 43/66] [release/v1.3] update site to use v1.3.3 (#5980) update site to use v1.3.3 Signed-off-by: Guy Daich Signed-off-by: Arko Dasgupta --- site/layouts/shortcodes/helm-version.html | 4 ++-- site/layouts/shortcodes/yaml-version.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/site/layouts/shortcodes/helm-version.html b/site/layouts/shortcodes/helm-version.html index 97289055ee..72fc4f6b30 100644 --- a/site/layouts/shortcodes/helm-version.html +++ b/site/layouts/shortcodes/helm-version.html @@ -9,8 +9,8 @@ {{- "v1.2.6" -}} {{- end -}} {{- with (strings.HasPrefix $pagePrefix "v1.3") -}} -{{- "v1.3.2" -}} +{{- "v1.3.3" -}} {{- end -}} {{- with (strings.HasPrefix $pagePrefix "docs") -}} -{{- "v1.3.2" -}} +{{- "v1.3.3" -}} {{- end -}} diff --git a/site/layouts/shortcodes/yaml-version.html b/site/layouts/shortcodes/yaml-version.html index c69618496f..d06602f40e 100644 --- a/site/layouts/shortcodes/yaml-version.html +++ b/site/layouts/shortcodes/yaml-version.html @@ -9,8 +9,8 @@ {{- "v1.2.6" -}} {{- end -}} {{- with (strings.HasPrefix $pagePrefix "v1.3") -}} -{{- "v1.3.2" -}} +{{- "v1.3.3" -}} {{- end -}} {{- with (strings.HasPrefix $pagePrefix "docs") -}} -{{- "v1.3.2" -}} +{{- "v1.3.3" -}} {{- end -}} From bb8007a8cb64d607bd29ddd5f9ff843911e21648 Mon Sep 17 00:00:00 2001 From: "Huabing (Robin) Zhao" Date: Sat, 10 May 2025 09:07:15 +0800 Subject: [PATCH 44/66] docs: dynamic resolver backend (#5935) * docs for dynamic resolver backend Signed-off-by: Huabing (Robin) Zhao * update docs Signed-off-by: Huabing (Robin) Zhao * update docs Signed-off-by: Huabing (Robin) Zhao * delete docs Signed-off-by: Huabing (Robin) Zhao --------- Signed-off-by: Huabing (Robin) Zhao Signed-off-by: Arko Dasgupta --- .../en/latest/tasks/traffic/backend.md | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/site/content/en/latest/tasks/traffic/backend.md b/site/content/en/latest/tasks/traffic/backend.md index 55d125a27a..4d0c7c88e2 100644 --- a/site/content/en/latest/tasks/traffic/backend.md +++ b/site/content/en/latest/tasks/traffic/backend.md @@ -16,6 +16,7 @@ A Backend resource can be used to: - Expose a Service or Pod that should not be accessible - Reference a Service or Pod by a Route without appropriate Reference Grants - Expose the Envoy Proxy localhost (including the Envoy admin endpoint) +- When configured as the `DynamicResolver` type, it can route traffic to any destination, effectively exposing all potential endpoints to clients. This can introduce security risks if not properly managed. For these reasons, the Backend API is disabled by default in Envoy Gateway configuration. Envoy Gateway admins are advised to follow [upstream recommendations][] and restrict access to the Backend API using K8s RBAC. @@ -195,6 +196,103 @@ Send a request and view the response: curl -I -HHost:www.example.com http://${GATEWAY_HOST}/headers ``` +### Dynamic Forward Proxy + +Envoy Gateway can be configured as a dynamic forward proxy using the [Backend][] API by setting its type to `DynamicResolver`. +This allows Envoy Gateway to act as an HTTP proxy without needing prior knowledge of destination hostnames or IP addresses, +while still maintaining its advanced routing and traffic management capabilities. + +Under the hood, Envoy Gateway uses the Envoy [Dynamic Forward Proxy](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/http/http_proxy) +to implement this feature. + +In the following example, we will create a `HTTPRoute` that references a `Backend` resource of type `DynamicResolver`. +This setup allows Envoy Gateway to dynamically resolve the hostname in the request and forward the traffic to the original +destination of the request. + +Note: the TLS configuration in the following example is optional. It's only required if you want to use TLS to connect +to the backend service. The example uses the system well-known CA certificate to validate the backend service's certificate. +You can also use a custom CA certificate by specifying the `caCertificate` field in the `tls` section. + +{{< tabpane text=true >}} +{{% tab header="Apply from stdin" %}} + +```shell +cat <}} + +Get the Gateway address: + +```shell +export GATEWAY_HOST=$(kubectl get gateway/eg -o jsonpath='{.status.addresses[0].value}') +``` + +Send a request to `gateway.envoyproxy.io` and view the response: + +```shell +curl -HHost:gateway.envoyproxy.io http://${GATEWAY_HOST} +``` + +You can also send a request to any other domain, and Envoy Gateway will resolve the hostname and route the traffic accordingly: + +```shell +curl -HHost:httpbin.org http://${GATEWAY_HOST}/get +``` + [Backend]: ../../../api/extension_types#backend [routing to cluster-external backends]: ./../../tasks/traffic/routing-outside-kubernetes.md [BackendObjectReference]: https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.BackendObjectReference From 16159efd9588ff56143605f673524b40ccb579ee Mon Sep 17 00:00:00 2001 From: Sudipto Baral Date: Fri, 9 May 2025 23:58:24 -0400 Subject: [PATCH 45/66] Fuzzing: Fail on xds translation error (#5986) Fail on xds translation error Signed-off-by: sudipto baral Signed-off-by: Arko Dasgupta --- test/fuzz/xds_fuzz_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/fuzz/xds_fuzz_test.go b/test/fuzz/xds_fuzz_test.go index 1dd549e3e5..38885226f2 100644 --- a/test/fuzz/xds_fuzz_test.go +++ b/test/fuzz/xds_fuzz_test.go @@ -6,6 +6,7 @@ package fuzz import ( + "strings" "testing" fuzz "github.com/AdaLogics/go-fuzz-headers" @@ -25,6 +26,9 @@ func FuzzGatewayAPIToXDS(f *testing.F) { dnsDomain, _ := fc.GetString() resourceType, _ := fc.GetString() - _, _ = egctl.TranslateGatewayAPIToXds(namespace, dnsDomain, resourceType, rs) + _, err = egctl.TranslateGatewayAPIToXds(namespace, dnsDomain, resourceType, rs) + if err != nil && strings.Contains(err.Error(), "failed to translate xds") { + t.Fatalf("%v", err) + } }) } From c59a8c2db5db09ff12826dcb31bd25f6232b7ebd Mon Sep 17 00:00:00 2001 From: zirain Date: Sat, 10 May 2025 19:27:28 +0800 Subject: [PATCH 46/66] fix btp merge not working when there's multi parent refs on router (#5967) * fix btp merge not working when there's multi parent refs on router Signed-off-by: zirain * address comment Signed-off-by: zirain * messge Signed-off-by: zirain --------- Signed-off-by: zirain Signed-off-by: Arko Dasgupta --- internal/gatewayapi/backendtrafficpolicy.go | 34 +- ...strategic-merge-with-multi-parents.in.yaml | 89 ++++ ...trategic-merge-with-multi-parents.out.yaml | 386 ++++++++++++++++++ ...kendtrafficpolicy-strategic-merge.out.yaml | 9 +- 4 files changed, 506 insertions(+), 12 deletions(-) create mode 100644 internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-with-multi-parents.in.yaml create mode 100644 internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-with-multi-parents.out.yaml diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go index 6a6565cd81..75fcd286a9 100644 --- a/internal/gatewayapi/backendtrafficpolicy.go +++ b/internal/gatewayapi/backendtrafficpolicy.go @@ -165,7 +165,7 @@ func (t *Translator) ProcessBackendTrafficPolicies(resources *resource.Resources if policy.Spec.MergeType == nil { // Set conditions for translation error if it got any - if err := t.translateBackendTrafficPolicyForRoute(policy, route, xdsIR, resources); err != nil { + if err := t.translateBackendTrafficPolicyForRoute(policy, route, xdsIR, resources, nil); err != nil { status.SetTranslationErrorForPolicyAncestors(&policy.Status, ancestorRefs, t.GatewayControllerName, @@ -176,28 +176,34 @@ func (t *Translator) ProcessBackendTrafficPolicies(resources *resource.Resources } else { // merge with parent target policy if exists for _, gwNN := range routeParents.UnsortedList() { + ancestorRef := getAncestorRefForPolicy(gwNN, nil) // find policy for Gateway gwPolicy := gatewayPolicyMap[gwNN] if gwPolicy == nil { // not found, fall back to the current policy - if err := t.translateBackendTrafficPolicyForRoute(policy, route, xdsIR, resources); err != nil { - status.SetTranslationErrorForPolicyAncestors(&policy.Status, - ancestorRefs, + if err := t.translateBackendTrafficPolicyForRoute(policy, route, xdsIR, resources, &gwNN); err != nil { + status.SetConditionForPolicyAncestor(&policy.Status, + ancestorRef, t.GatewayControllerName, - policy.Generation, + gwapiv1a2.PolicyConditionAccepted, metav1.ConditionFalse, + egv1a1.PolicyReasonInvalid, status.Error2ConditionMsg(err), + policy.Generation, ) } + continue } // merge with parent policy if err := t.translateBackendTrafficPolicyForRouteWithMerge(policy, gwNN, gwPolicy, route, xdsIR, resources); err != nil { - status.SetTranslationErrorForPolicyAncestors(&policy.Status, - ancestorRefs, + status.SetConditionForPolicyAncestor(&policy.Status, + ancestorRef, t.GatewayControllerName, - policy.Generation, + gwapiv1a2.PolicyConditionAccepted, metav1.ConditionFalse, + egv1a1.PolicyReasonInvalid, status.Error2ConditionMsg(err), + policy.Generation, ) continue } @@ -207,8 +213,8 @@ func (t *Translator) ProcessBackendTrafficPolicies(resources *resource.Resources } gatewayPolicyMerged[gwNN].Insert(utils.NamespacedName(route).String()) - status.SetConditionForPolicyAncestors(&policy.Status, - ancestorRefs, + status.SetConditionForPolicyAncestor(&policy.Status, + ancestorRef, t.GatewayControllerName, egv1a1.PolicyConditionMerged, metav1.ConditionTrue, @@ -393,6 +399,7 @@ func (t *Translator) translateBackendTrafficPolicyForRoute( route RouteContext, xdsIR resource.XdsIRMap, resources *resource.Resources, + gatewayNN *types.NamespacedName, ) error { tf, errs := t.buildTrafficFeatures(policy, resources) if tf == nil { @@ -401,7 +408,12 @@ func (t *Translator) translateBackendTrafficPolicyForRoute( } // Apply IR to all relevant routes - for _, x := range xdsIR { + for key, x := range xdsIR { + // if gatewayNN is not nil, only apply to the specific gateway + if gatewayNN != nil && key != t.IRKey(*gatewayNN) { + // Skip if not the gateway wanted + continue + } applyTrafficFeatureToRoute(route, tf, errs, policy, x) } diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-with-multi-parents.in.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-with-multi-parents.in.yaml new file mode 100644 index 0000000000..e9ac3501cf --- /dev/null +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-with-multi-parents.in.yaml @@ -0,0 +1,89 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All + - apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-2 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +httpRoutes: + - apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + - namespace: envoy-gateway + name: gateway-2 + sectionName: http + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 +backendTrafficPolicies: + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: envoy-gateway + name: policy-for-gateway1 + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + timeout: + tcp: + connectTimeout: 15s + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + httpUpgrade: + - type: websocket + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: default + name: policy-for-route + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + mergeType: StrategicMerge + timeout: + tcp: + connectTimeout: 10s + connection: + bufferLimit: 100M + httpUpgrade: + - type: "spdy/3.1" diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-with-multi-parents.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-with-multi-parents.out.yaml new file mode 100644 index 0000000000..70ff8aee37 --- /dev/null +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-with-multi-parents.out.yaml @@ -0,0 +1,386 @@ +backendTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + creationTimestamp: null + name: policy-for-route + namespace: default + spec: + connection: + bufferLimit: 100M + httpUpgrade: + - type: spdy/3.1 + mergeType: StrategicMerge + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + timeout: + tcp: + connectTimeout: 10s + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Merged with policy envoy-gateway/policy-for-gateway1 + reason: Merged + status: "True" + type: Merged + controllerName: gateway.envoyproxy.io/gatewayclass-controller + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-2 + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + creationTimestamp: null + name: policy-for-gateway1 + namespace: envoy-gateway + spec: + httpUpgrade: + - type: websocket + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + timeout: + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + tcp: + connectTimeout: 15s + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: 'This policy is being merged by other backendTrafficPolicies for + these routes: [default/httproute-1]' + reason: Merged + status: "True" + type: Merged + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-2 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + - name: gateway-2 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-2 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system + envoy-gateway/gateway-2: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-2/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-2 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-2 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + traffic: + backendConnection: + bufferLimit: 100000000 + httpUpgrade: + - spdy/3.1 + - websocket + name: default/policy-for-route + timeout: + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + tcp: + connectTimeout: 10s + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 + envoy-gateway/gateway-2: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-2 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-2/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + traffic: + backendConnection: + bufferLimit: 100000000 + httpUpgrade: + - spdy/3.1 + name: default/policy-for-route + timeout: + tcp: + connectTimeout: 10s + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge.out.yaml index 1d876fb04b..8905ab639d 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge.out.yaml @@ -25,13 +25,20 @@ backendTrafficPolicies: kind: Gateway name: gateway-1 namespace: envoy-gateway - sectionName: http conditions: - lastTransitionTime: null message: Merged with policy envoy-gateway/policy-for-gateway reason: Merged status: "True" type: Merged + controllerName: gateway.envoyproxy.io/gatewayclass-controller + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + conditions: - lastTransitionTime: null message: Policy has been accepted. reason: Accepted From 86c539ebd8c98d80f58dddc2ac575853b1c246e9 Mon Sep 17 00:00:00 2001 From: zirain Date: Sat, 10 May 2025 19:27:35 +0800 Subject: [PATCH 47/66] e2e: fix GRPCExtAuth flaky (#5987) Signed-off-by: zirain Signed-off-by: Arko Dasgupta --- test/e2e/tests/ext_auth_grpc_service.go | 30 +++---------------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/test/e2e/tests/ext_auth_grpc_service.go b/test/e2e/tests/ext_auth_grpc_service.go index 8f846cb509..7c6ec207a8 100644 --- a/test/e2e/tests/ext_auth_grpc_service.go +++ b/test/e2e/tests/ext_auth_grpc_service.go @@ -86,15 +86,7 @@ var GRPCExtAuthTest = suite.ConformanceTest{ Namespace: ns, } - req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http") - cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req) - if err != nil { - t.Errorf("failed to get expected response: %v", err) - } - - if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil { - t.Errorf("failed to compare request and response: %v", err) - } + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse) }) t.Run("invalid credential", func(t *testing.T) { @@ -112,15 +104,7 @@ var GRPCExtAuthTest = suite.ConformanceTest{ Namespace: ns, } - req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http") - cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req) - if err != nil { - t.Errorf("failed to get expected response: %v", err) - } - - if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil { - t.Errorf("failed to compare request and response: %v", err) - } + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse) }) t.Run("http route without ext auth authentication", func(t *testing.T) { @@ -135,15 +119,7 @@ var GRPCExtAuthTest = suite.ConformanceTest{ Namespace: ns, } - req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http") - cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req) - if err != nil { - t.Errorf("failed to get expected response: %v", err) - } - - if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil { - t.Errorf("failed to compare request and response: %v", err) - } + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse) }) }, } From a38d68aefe3c86be85dd6625eb1af0d6d76165ee Mon Sep 17 00:00:00 2001 From: sh2 Date: Sun, 11 May 2025 05:48:48 +0800 Subject: [PATCH 48/66] chore: add coverpkg for coverage test (#5991) add coverpkg for coverage test Signed-off-by: shawnh2 Signed-off-by: Arko Dasgupta --- tools/make/golang.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/make/golang.mk b/tools/make/golang.mk index 50a1825275..7255afa64e 100644 --- a/tools/make/golang.mk +++ b/tools/make/golang.mk @@ -69,7 +69,7 @@ go.testdata.complete: ## Override test ouputdata go.test.coverage: go.test.cel ## Run go unit and integration tests in GitHub Actions @$(LOG_TARGET) KUBEBUILDER_ASSETS="$(shell go tool setup-envtest use $(ENVTEST_K8S_VERSION) -p path)" \ - go test ./... --tags=integration -race -coverprofile=coverage.xml -covermode=atomic + go test ./... --tags=integration -race -coverprofile=coverage.xml -covermode=atomic -coverpkg=./... .PHONY: go.test.cel go.test.cel: manifests # Run the CEL validation tests From ce44ad2f700dcd605c7858200ceb85972ead281f Mon Sep 17 00:00:00 2001 From: zirain Date: Mon, 12 May 2025 09:42:22 +0800 Subject: [PATCH 49/66] ci: enable conformance test for GatewayNamespaceMode (#5992) Signed-off-by: zirain Signed-off-by: Arko Dasgupta --- .github/workflows/build_and_test.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml index 580d2d7413..651841c7bf 100644 --- a/.github/workflows/build_and_test.yaml +++ b/.github/workflows/build_and_test.yaml @@ -88,13 +88,20 @@ jobs: target: - version: v1.30.10 ipFamily: ipv4 + profile: default - version: v1.31.6 ipFamily: ipv4 + profile: default - version: v1.32.3 ipFamily: ipv6 # only run ipv6 test on this version to save time + profile: default # TODO: this's IPv4 first, need a way to test IPv6 first. - version: v1.33.0 ipFamily: dual # only run dual test on latest version to save time + profile: default + - version: v1.33.0 + ipFamily: dual # only run dual test on latest version to save time + profile: gateway-namespace-mode steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: ./tools/github-actions/setup-deps @@ -116,6 +123,7 @@ jobs: KIND_NODE_TAG: ${{ matrix.target.version }} IMAGE_PULL_POLICY: IfNotPresent IP_FAMILY: ${{ matrix.target.ipFamily }} + KUBE_DEPLOY_PROFILE: ${{ matrix.target.profile }} run: make conformance e2e-test: From bca5592c89c7b6ce471bd5fab3be01ce880cb453 Mon Sep 17 00:00:00 2001 From: zirain Date: Mon, 12 May 2025 10:48:38 +0800 Subject: [PATCH 50/66] e2e: add CollectAndDump for EGUpgrade test (#5998) * e2e: only run collect and dump when failed Signed-off-by: zirain * dump when EGUpgrade failed Signed-off-by: zirain * nit Signed-off-by: zirain --------- Signed-off-by: zirain Signed-off-by: Arko Dasgupta --- test/e2e/tests/eg_upgrade.go | 5 +++++ test/e2e/tests/merge_gateways.go | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/test/e2e/tests/eg_upgrade.go b/test/e2e/tests/eg_upgrade.go index f1d99b1509..7e37aa4547 100644 --- a/test/e2e/tests/eg_upgrade.go +++ b/test/e2e/tests/eg_upgrade.go @@ -162,6 +162,11 @@ var EGUpgradeTest = suite.ConformanceTest{ t.Errorf("failed to get expected response for the first three requests: %v", err) } }) + t.Cleanup(func() { + if t.Failed() { + CollectAndDump(t, suite.RestConfig) + } + }) }, } diff --git a/test/e2e/tests/merge_gateways.go b/test/e2e/tests/merge_gateways.go index 018856d0fc..1e62f57067 100644 --- a/test/e2e/tests/merge_gateways.go +++ b/test/e2e/tests/merge_gateways.go @@ -246,8 +246,10 @@ var MergeGatewaysTest = suite.ConformanceTest{ // Clean-up the conflicted gateway and route resources. t.Cleanup(func() { - // Collect and dump every config before removing the created resource. - CollectAndDump(t, suite.RestConfig) + if t.Failed() { + // Collect and dump every config before removing the created resource. + CollectAndDump(t, suite.RestConfig) + } conflictedGateway := new(gwapiv1.Gateway) conflictedHTTPRoute := new(gwapiv1.HTTPRoute) From 13c72ec87c9bfe495110635a6f6df5cf2de49474 Mon Sep 17 00:00:00 2001 From: zirain Date: Mon, 12 May 2025 12:08:01 +0800 Subject: [PATCH 51/66] e2e: add test for BTP timeout (#5994) Signed-off-by: zirain Signed-off-by: Arko Dasgupta --- test/e2e/testdata/btp-timeout.yaml | 30 ++++++++++++++++++ test/e2e/testdata/client-timeout.yaml | 2 +- test/e2e/tests/btp_timeout.go | 42 +++++++++++++++++++++++++ test/e2e/tests/client_timeout.go | 39 +++--------------------- test/e2e/tests/utils.go | 44 +++++++++++++++++++++++++-- 5 files changed, 119 insertions(+), 38 deletions(-) create mode 100644 test/e2e/testdata/btp-timeout.yaml create mode 100644 test/e2e/tests/btp_timeout.go diff --git a/test/e2e/testdata/btp-timeout.yaml b/test/e2e/testdata/btp-timeout.yaml new file mode 100644 index 0000000000..19f0cd541b --- /dev/null +++ b/test/e2e/testdata/btp-timeout.yaml @@ -0,0 +1,30 @@ +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: timeout + namespace: gateway-conformance-infra +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: http-btp-timeout + timeout: + http: + requestTimeout: 2s +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: http-btp-timeout + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + rules: + - matches: + - path: + type: PathPrefix + value: /btp-timeout + backendRefs: + - name: infra-backend-v1 + port: 8080 diff --git a/test/e2e/testdata/client-timeout.yaml b/test/e2e/testdata/client-timeout.yaml index 43cf26ec3c..eea250993b 100644 --- a/test/e2e/testdata/client-timeout.yaml +++ b/test/e2e/testdata/client-timeout.yaml @@ -38,7 +38,7 @@ spec: - matches: - path: type: PathPrefix - value: /request-timeout + value: /client-timeout backendRefs: - name: infra-backend-v1 port: 8080 diff --git a/test/e2e/tests/btp_timeout.go b/test/e2e/tests/btp_timeout.go new file mode 100644 index 0000000000..0baaa72ada --- /dev/null +++ b/test/e2e/tests/btp_timeout.go @@ -0,0 +1,42 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +//go:build e2e + +package tests + +import ( + "net/http" + "testing" + + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" + "sigs.k8s.io/gateway-api/conformance/utils/suite" +) + +func init() { + ConformanceTests = append(ConformanceTests, BTPTimeoutTest) +} + +var BTPTimeoutTest = suite.ConformanceTest{ + ShortName: "BTPTimeout", + Description: "Test BackendTrafficPolicy timeout", + Manifests: []string{"testdata/btp-timeout.yaml"}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + t.Run("HTTP", func(t *testing.T) { + routeNN := types.NamespacedName{Name: "http-btp-timeout", Namespace: ConformanceInfraNamespace} + gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, + SameNamespaceGatewayRef, routeNN) + + ExpectRequestTimeout(t, suite, gwAddr, "/btp-timeout", "", http.StatusOK) + // Timeout is 2s, so deplay 1s will return 200 + ExpectRequestTimeout(t, suite, gwAddr, "/btp-timeout", "delay=1s", http.StatusOK) + // Timeout is 2s, so deplay 4s will return 504 + ExpectRequestTimeout(t, suite, gwAddr, "/btp-timeout", "delay=4s", http.StatusGatewayTimeout) + }) + + // TODO: add test for TCP + }, +} diff --git a/test/e2e/tests/client_timeout.go b/test/e2e/tests/client_timeout.go index faffec9df7..e655d1398d 100644 --- a/test/e2e/tests/client_timeout.go +++ b/test/e2e/tests/client_timeout.go @@ -9,15 +9,11 @@ package tests import ( "net/http" - "net/url" "testing" - "time" "k8s.io/apimachinery/pkg/types" - httputils "sigs.k8s.io/gateway-api/conformance/utils/http" "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" "sigs.k8s.io/gateway-api/conformance/utils/suite" - "sigs.k8s.io/gateway-api/conformance/utils/tlog" ) func init() { @@ -30,38 +26,11 @@ var ClientTimeoutTest = suite.ConformanceTest{ Manifests: []string{"testdata/client-timeout.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { t.Run("http client timeout", func(t *testing.T) { - ns := "gateway-conformance-infra" - routeNN := types.NamespacedName{Name: "http-client-timeout", Namespace: ns} - gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} - gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) + routeNN := types.NamespacedName{Name: "http-client-timeout", Namespace: ConformanceInfraNamespace} + gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, + SameNamespaceGatewayRef, routeNN) - // Use raw http request to avoid chunked - req := &http.Request{ - Method: "GET", - URL: &url.URL{Scheme: "http", Host: gwAddr, Path: "/request-timeout"}, - } - - client := &http.Client{} - - httputils.AwaitConvergence(t, - suite.TimeoutConfig.RequiredConsecutiveSuccesses, - suite.TimeoutConfig.MaxTimeToConsistency, - func(elapsed time.Duration) bool { - resp, err := client.Do(req) - if err != nil { - panic(err) - } - defer resp.Body.Close() - - // return 504 instead of 400 when request timeout. - // https://github.com/envoyproxy/envoy/blob/56021dbfb10b53c6d08ed6fc811e1ff4c9ac41fd/source/common/http/utility.cc#L1409 - if http.StatusGatewayTimeout == resp.StatusCode { - return true - } else { - tlog.Logf(t, "response status code: %d, (after %v) ", resp.StatusCode, elapsed) - return false - } - }) + ExpectRequestTimeout(t, suite, gwAddr, "/client-timeout", "", http.StatusGatewayTimeout) }) }, } diff --git a/test/e2e/tests/utils.go b/test/e2e/tests/utils.go index 5700f82af9..1a628a2155 100644 --- a/test/e2e/tests/utils.go +++ b/test/e2e/tests/utils.go @@ -38,6 +38,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" "sigs.k8s.io/gateway-api/conformance/utils/config" + httputils "sigs.k8s.io/gateway-api/conformance/utils/http" + k8sutils "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" "sigs.k8s.io/gateway-api/conformance/utils/suite" "sigs.k8s.io/gateway-api/conformance/utils/tlog" @@ -49,11 +51,18 @@ import ( var ( IPFamily = os.Getenv("IP_FAMILY") DeployProfile = os.Getenv("KUBE_DEPLOY_PROFILE") + + SameNamespaceGateway = types.NamespacedName{Name: "same-namespace", Namespace: ConformanceInfraNamespace} + SameNamespaceGatewayRef = k8sutils.NewGatewayRef(SameNamespaceGateway) + + PodReady = corev1.PodCondition{Type: corev1.PodReady, Status: corev1.ConditionTrue} ) -const defaultServiceStartupTimeout = 5 * time.Minute +const ( + ConformanceInfraNamespace = "gateway-conformance-infra" -var PodReady = corev1.PodCondition{Type: corev1.PodReady, Status: corev1.ConditionTrue} + defaultServiceStartupTimeout = 5 * time.Minute +) // WaitForPods waits for the pods in the given namespace and with the given selector // to be in the given phase and condition. @@ -702,3 +711,34 @@ func GetGatewayResourceNamespace() string { } return "envoy-gateway-system" } + +func ExpectRequestTimeout(t *testing.T, suite *suite.ConformanceTestSuite, gwAddr, path, query string, exceptedStatusCode int) { + // Use raw http request to avoid chunked + req := &http.Request{ + Method: "GET", + URL: &url.URL{Scheme: "http", Host: gwAddr, Path: path, RawQuery: query}, + } + + client := &http.Client{ + Timeout: 10 * time.Second, + } + httputils.AwaitConvergence(t, suite.TimeoutConfig.RequiredConsecutiveSuccesses, suite.TimeoutConfig.MaxTimeToConsistency, + func(elapsed time.Duration) bool { + resp, err := client.Do(req) + if err != nil { + panic(err) + } + defer func() { + _ = resp.Body.Close() + }() + + // return 504 instead of 400 when request timeout. + // https://github.com/envoyproxy/envoy/blob/56021dbfb10b53c6d08ed6fc811e1ff4c9ac41fd/source/common/http/utility.cc#L1409 + if exceptedStatusCode == resp.StatusCode { + return true + } else { + tlog.Logf(t, "%s%s response status code: %d after %v", gwAddr, path, resp.StatusCode, elapsed) + return false + } + }) +} From 3bee79cc8301edc26567c05a7d645bec30ad576e Mon Sep 17 00:00:00 2001 From: zirain Date: Mon, 12 May 2025 13:18:35 +0800 Subject: [PATCH 52/66] Remoe check for accesslog formatter (#5985) * Remoe check for accesslog formatter Signed-off-by: zirain * gen Signed-off-by: zirain * lint Signed-off-by: zirain --------- Signed-off-by: zirain Signed-off-by: Arko Dasgupta --- internal/xds/translator/accesslog.go | 106 ++---------------- .../accesslog-formatters.listeners.yaml | 28 ----- 2 files changed, 11 insertions(+), 123 deletions(-) diff --git a/internal/xds/translator/accesslog.go b/internal/xds/translator/accesslog.go index e707c24dac..ef9ffca1e1 100644 --- a/internal/xds/translator/accesslog.go +++ b/internal/xds/translator/accesslog.go @@ -16,8 +16,6 @@ import ( cel "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/filters/cel/v3" grpcaccesslog "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/grpc/v3" otelaccesslog "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/open_telemetry/v3" - celformatter "github.com/envoyproxy/go-control-plane/envoy/extensions/formatter/cel/v3" - metadataformatter "github.com/envoyproxy/go-control-plane/envoy/extensions/formatter/metadata/v3" reqwithoutqueryformatter "github.com/envoyproxy/go-control-plane/envoy/extensions/formatter/req_without_query/v3" "github.com/envoyproxy/go-control-plane/pkg/wellknown" otlpcommonv1 "go.opentelemetry.io/proto/otlp/common/v1" @@ -35,8 +33,6 @@ const ( otelAccessLog = "envoy.access_loggers.open_telemetry" reqWithoutQueryCommandOperator = "%REQ_WITHOUT_QUERY" - metadataCommandOperator = "%METADATA" - celCommandOperator = "%CEL" tcpGRPCAccessLog = "envoy.access_loggers.tcp_grpc" celFilter = "envoy.access_loggers.extension_filters.cel" @@ -76,45 +72,17 @@ var listenerAccessLogFilter = &accesslog.AccessLogFilter{ }, } -var ( - // reqWithoutQueryFormatter configures additional formatters needed for some of the format strings like "REQ_WITHOUT_QUERY" - reqWithoutQueryFormatter *cfgcore.TypedExtensionConfig - - // metadataFormatter configures additional formatters needed for some of the format strings like "METADATA" - // for more information, see https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/formatter/metadata/v3/metadata.proto - metadataFormatter *cfgcore.TypedExtensionConfig - - // celFormatter configures additional formatters needed for some of the format strings like "CEL" - // for more information, see https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/formatter/cel/v3/cel.proto - celFormatter *cfgcore.TypedExtensionConfig -) +// reqWithoutQueryFormatter configures additional formatters needed for some of the format strings like "REQ_WITHOUT_QUERY" +var reqWithoutQueryFormatter *cfgcore.TypedExtensionConfig func init() { - any, err := proto.ToAnyWithValidation(&reqwithoutqueryformatter.ReqWithoutQuery{}) + anyCfg, err := proto.ToAnyWithValidation(&reqwithoutqueryformatter.ReqWithoutQuery{}) if err != nil { panic(err) } reqWithoutQueryFormatter = &cfgcore.TypedExtensionConfig{ Name: "envoy.formatter.req_without_query", - TypedConfig: any, - } - - any, err = proto.ToAnyWithValidation(&metadataformatter.Metadata{}) - if err != nil { - panic(err) - } - metadataFormatter = &cfgcore.TypedExtensionConfig{ - Name: "envoy.formatter.metadata", - TypedConfig: any, - } - - any, err = proto.ToAnyWithValidation(&celformatter.Cel{}) - if err != nil { - panic(err) - } - celFormatter = &cfgcore.TypedExtensionConfig{ - Name: "envoy.formatter.cel", - TypedConfig: any, + TypedConfig: anyCfg, } } @@ -388,7 +356,7 @@ func celAccessLogFilter(expr string) (*accesslog.AccessLogFilter, error) { fl := &cel.ExpressionFilter{ Expression: expr, } - any, err := proto.ToAnyWithValidation(fl) + anyCfg, err := proto.ToAnyWithValidation(fl) if err != nil { return nil, err } @@ -397,7 +365,7 @@ func celAccessLogFilter(expr string) (*accesslog.AccessLogFilter, error) { FilterSpecifier: &accesslog.AccessLogFilter_ExtensionFilter{ ExtensionFilter: &accesslog.ExtensionFilter{ Name: celFilter, - ConfigType: &accesslog.ExtensionFilter_TypedConfig{TypedConfig: any}, + ConfigType: &accesslog.ExtensionFilter_TypedConfig{TypedConfig: anyCfg}, }, }, }, nil @@ -441,36 +409,20 @@ func accessLogTextFormatters(text string) []*cfgcore.TypedExtensionConfig { formatters = append(formatters, reqWithoutQueryFormatter) } - if strings.Contains(text, metadataCommandOperator) { - formatters = append(formatters, metadataFormatter) - } - - if strings.Contains(text, celCommandOperator) { - formatters = append(formatters, celFormatter) - } - return formatters } func accessLogJSONFormatters(json map[string]string) []*cfgcore.TypedExtensionConfig { - reqWithoutQuery, metadata, cel := false, false, false + reqWithoutQuery := false for _, value := range json { - if reqWithoutQuery && metadata && cel { + if reqWithoutQuery { break } if strings.Contains(value, reqWithoutQueryCommandOperator) { reqWithoutQuery = true } - - if strings.Contains(value, metadataCommandOperator) { - metadata = true - } - - if strings.Contains(value, celCommandOperator) { - cel = true - } } formatters := make([]*cfgcore.TypedExtensionConfig, 0, 3) @@ -479,64 +431,28 @@ func accessLogJSONFormatters(json map[string]string) []*cfgcore.TypedExtensionCo formatters = append(formatters, reqWithoutQueryFormatter) } - if metadata { - formatters = append(formatters, metadataFormatter) - } - - if cel { - formatters = append(formatters, celFormatter) - } - return formatters } func accessLogOpenTelemetryFormatters(body string, attributes map[string]string) []*cfgcore.TypedExtensionConfig { - reqWithoutQuery, metadata, cel := false, false, false + var reqWithoutQuery bool if strings.Contains(body, reqWithoutQueryCommandOperator) { reqWithoutQuery = true } - if strings.Contains(body, metadataCommandOperator) { - metadata = true - } - - if strings.Contains(body, celCommandOperator) { - cel = true - } - for _, value := range attributes { - if reqWithoutQuery && metadata && cel { - break - } - - if !reqWithoutQuery && strings.Contains(value, reqWithoutQueryCommandOperator) { + if strings.Contains(value, reqWithoutQueryCommandOperator) { reqWithoutQuery = true - } - - if !metadata && strings.Contains(value, metadataCommandOperator) { - metadata = true - } - - if !cel && strings.Contains(value, celCommandOperator) { - cel = true + break } } formatters := make([]*cfgcore.TypedExtensionConfig, 0, 3) - if reqWithoutQuery { formatters = append(formatters, reqWithoutQueryFormatter) } - if metadata { - formatters = append(formatters, metadataFormatter) - } - - if cel { - formatters = append(formatters, celFormatter) - } - return formatters } diff --git a/internal/xds/translator/testdata/out/xds-ir/accesslog-formatters.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/accesslog-formatters.listeners.yaml index 6efcf6de18..7e51cbeb1d 100644 --- a/internal/xds/translator/testdata/out/xds-ir/accesslog-formatters.listeners.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/accesslog-formatters.listeners.yaml @@ -7,10 +7,6 @@ typedConfig: '@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog logFormat: - formatters: - - name: envoy.formatter.cel - typedConfig: - '@type': type.googleapis.com/envoy.extensions.formatter.cel.v3.Cel textFormatSource: inlineString: | [%START_TIME%] "%CEL(request.method)% %CEL(request.path)% %CEL(request.protocol)%" %CEL(response.code)% %CEL(response.flags)% %CEL(request.total_size)% %CEL(response.total_size)% %CEL(request.duration)% %CEL(response.headers['X-ENVOY-UPSTREAM-SERVICE-TIME'])% "%CEL(request.headers['X-FORWARDED-FOR'])%" "%CEL(request.useragent)%" "%CEL(request.id)%" "%CEL(request.host)%" "%CEL(upstream.address)%" @@ -23,10 +19,6 @@ typedConfig: '@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog logFormat: - formatters: - - name: envoy.formatter.metadata - typedConfig: - '@type': type.googleapis.com/envoy.extensions.formatter.metadata.v3.Metadata jsonFormat: method: '%REQ(:METHOD)%' path: '%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%' @@ -92,12 +84,6 @@ - name: envoy.formatter.req_without_query typedConfig: '@type': type.googleapis.com/envoy.extensions.formatter.req_without_query.v3.ReqWithoutQuery - - name: envoy.formatter.metadata - typedConfig: - '@type': type.googleapis.com/envoy.extensions.formatter.metadata.v3.Metadata - - name: envoy.formatter.cel - typedConfig: - '@type': type.googleapis.com/envoy.extensions.formatter.cel.v3.Cel resourceAttributes: values: - key: cluster_name @@ -117,10 +103,6 @@ typedConfig: '@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog logFormat: - formatters: - - name: envoy.formatter.cel - typedConfig: - '@type': type.googleapis.com/envoy.extensions.formatter.cel.v3.Cel textFormatSource: inlineString: | [%START_TIME%] "%CEL(request.method)% %CEL(request.path)% %CEL(request.protocol)%" %CEL(response.code)% %CEL(response.flags)% %CEL(request.total_size)% %CEL(response.total_size)% %CEL(request.duration)% %CEL(response.headers['X-ENVOY-UPSTREAM-SERVICE-TIME'])% "%CEL(request.headers['X-FORWARDED-FOR'])%" "%CEL(request.useragent)%" "%CEL(request.id)%" "%CEL(request.host)%" "%CEL(upstream.address)%" @@ -129,10 +111,6 @@ typedConfig: '@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog logFormat: - formatters: - - name: envoy.formatter.metadata - typedConfig: - '@type': type.googleapis.com/envoy.extensions.formatter.metadata.v3.Metadata jsonFormat: method: '%REQ(:METHOD)%' path: '%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%' @@ -190,12 +168,6 @@ - name: envoy.formatter.req_without_query typedConfig: '@type': type.googleapis.com/envoy.extensions.formatter.req_without_query.v3.ReqWithoutQuery - - name: envoy.formatter.metadata - typedConfig: - '@type': type.googleapis.com/envoy.extensions.formatter.metadata.v3.Metadata - - name: envoy.formatter.cel - typedConfig: - '@type': type.googleapis.com/envoy.extensions.formatter.cel.v3.Cel resourceAttributes: values: - key: cluster_name From ab2b84f4e99c73ac1d51ee80398683944edb1332 Mon Sep 17 00:00:00 2001 From: zirain Date: Mon, 12 May 2025 16:54:31 +0800 Subject: [PATCH 53/66] e2e: fix GRPCExtAuth/http_route_with_ext_auth_authentication (#6001) Signed-off-by: zirain Signed-off-by: Arko Dasgupta --- test/e2e/tests/ext_auth_grpc_service.go | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/test/e2e/tests/ext_auth_grpc_service.go b/test/e2e/tests/ext_auth_grpc_service.go index 7c6ec207a8..1a37a35f98 100644 --- a/test/e2e/tests/ext_auth_grpc_service.go +++ b/test/e2e/tests/ext_auth_grpc_service.go @@ -63,15 +63,7 @@ var GRPCExtAuthTest = suite.ConformanceTest{ Namespace: ns, } - req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http") - cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req) - if err != nil { - t.Errorf("failed to get expected response: %v", err) - } - - if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil { - t.Errorf("failed to compare request and response: %v", err) - } + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse) }) t.Run("without Authorization header", func(t *testing.T) { From eda784c72fda26269850d8602d2daaba5a3d7abc Mon Sep 17 00:00:00 2001 From: zirain Date: Mon, 12 May 2025 21:11:59 +0800 Subject: [PATCH 54/66] chore: update dependabot (#6007) Signed-off-by: zirain Signed-off-by: Arko Dasgupta --- .github/dependabot.yml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6559da3453..b1b6b5e969 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -36,19 +36,23 @@ updates: # Ignore github.com/docker/docker, since it should be updated alongside github.com/google/go-containerregistry - dependency-name: github.com/docker/docker groups: - github.com: + gomod: patterns: - "github.com*" + - "go.opentelemetry.io*" + - "golang*" + - "google*" + - "fortio.org*" + - "gopkg.in*" + - "helm.sh*" + - "gomodules.xyz" + exclude-patterns: + # Exclude envoyproxy/go-control-plane, since it needs more work to update + - "github.com/envoyproxy/go-control-plane*" k8s.io: patterns: - "k8s.io/*" - "sigs.k8s.io/*" - go.opentelemetry.io: - patterns: - - "go.opentelemetry.io*" - # Merge golang/google groups into group, since it should be updated alongside opentelemetry - - "golang*" - - "google*" - package-ecosystem: pip directories: - /tools/src/codespell From 884539954b448257b930ffeedabb3730bfe095a4 Mon Sep 17 00:00:00 2001 From: zirain Date: Mon, 12 May 2025 21:12:22 +0800 Subject: [PATCH 55/66] e2e: update CORS test (#6011) Signed-off-by: zirain Signed-off-by: Arko Dasgupta --- test/e2e/tests/cors.go | 129 +++++++++-------------------------------- 1 file changed, 26 insertions(+), 103 deletions(-) diff --git a/test/e2e/tests/cors.go b/test/e2e/tests/cors.go index 2d27a8efef..29a395a41a 100644 --- a/test/e2e/tests/cors.go +++ b/test/e2e/tests/cors.go @@ -30,7 +30,7 @@ var CORSFromSecurityPolicyTest = suite.ConformanceTest{ Description: "Test CORS from SecurityPolicy", Manifests: []string{"testdata/cors-security-policy.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { - testCORS(t, suite, true) + runCORStest(t, suite, true) }, } @@ -39,27 +39,31 @@ var CORSFromHTTPCORSFilterTest = suite.ConformanceTest{ Description: "Test CORS from HTTP CORS Filter", Manifests: []string{"testdata/cors-http-cors-filter.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { - testCORS(t, suite, false) + runCORStest(t, suite, false) }, } -func testCORS(t *testing.T, suite *suite.ConformanceTestSuite, isSP bool) { - t.Run("should enable cors with Allow Origin Exact", func(t *testing.T) { - ns := "gateway-conformance-infra" - routeNN := types.NamespacedName{Name: "http-with-cors-exact", Namespace: ns} - gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} - gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) - - ancestorRef := gwapiv1a2.ParentReference{ - Group: gatewayapi.GroupPtr(gwapiv1.GroupName), - Kind: gatewayapi.KindPtr(resource.KindGateway), - Namespace: gatewayapi.NamespacePtr(gwNN.Namespace), - Name: gwapiv1.ObjectName(gwNN.Name), - } - if isSP { - SecurityPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "cors-exact", Namespace: ns}, suite.ControllerName, ancestorRef) - } +func runCORStest(t *testing.T, suite *suite.ConformanceTestSuite, withSecurityPolicy bool) { + ns := "gateway-conformance-infra" + routeNN := types.NamespacedName{Name: "http-with-cors-exact", Namespace: ns} + gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} + gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) + + ancestorRef := gwapiv1a2.ParentReference{ + Group: gatewayapi.GroupPtr(gwapiv1.GroupName), + Kind: gatewayapi.KindPtr(resource.KindGateway), + Namespace: gatewayapi.NamespacePtr(gwNN.Namespace), + Name: gwapiv1.ObjectName(gwNN.Name), + } + + if withSecurityPolicy { + SecurityPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "cors-exact", Namespace: ns}, suite.ControllerName, ancestorRef) + } + if withSecurityPolicy { + SecurityPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "cors-exact", Namespace: ns}, suite.ControllerName, ancestorRef) + } + t.Run("should enable cors with Allow Origin Exact", func(t *testing.T) { expectedResponse := http.ExpectedResponse{ Request: http.Request{ Path: "/cors-exact", @@ -92,35 +96,10 @@ func testCORS(t *testing.T, suite *suite.ConformanceTestSuite, isSP bool) { }, Namespace: "", } - - req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http") - cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req) - if err != nil { - t.Errorf("failed to get expected response: %v", err) - } - - if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil { - t.Errorf("failed to compare request and response: %v", err) - } + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse) }) t.Run("should enable cors with Allow Origin Regex", func(t *testing.T) { - ns := "gateway-conformance-infra" - routeNN := types.NamespacedName{Name: "http-with-cors-exact", Namespace: ns} - gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} - gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) - - ancestorRef := gwapiv1a2.ParentReference{ - Group: gatewayapi.GroupPtr(gwapiv1.GroupName), - Kind: gatewayapi.KindPtr(resource.KindGateway), - Namespace: gatewayapi.NamespacePtr(gwNN.Namespace), - Name: gwapiv1.ObjectName(gwNN.Name), - } - - if isSP { - SecurityPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "cors-exact", Namespace: ns}, suite.ControllerName, ancestorRef) - } - expectedResponse := http.ExpectedResponse{ Request: http.Request{ Path: "/cors-exact", @@ -154,34 +133,10 @@ func testCORS(t *testing.T, suite *suite.ConformanceTestSuite, isSP bool) { Namespace: "", } - req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http") - cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req) - if err != nil { - t.Errorf("failed to get expected response: %v", err) - } - - if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil { - t.Errorf("failed to compare request and response: %v", err) - } + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse) }) t.Run("should not contain cors headers when Origin not registered", func(t *testing.T) { - ns := "gateway-conformance-infra" - routeNN := types.NamespacedName{Name: "http-with-cors-exact", Namespace: ns} - gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} - gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) - - ancestorRef := gwapiv1a2.ParentReference{ - Group: gatewayapi.GroupPtr(gwapiv1.GroupName), - Kind: gatewayapi.KindPtr(resource.KindGateway), - Namespace: gatewayapi.NamespacePtr(gwNN.Namespace), - Name: gwapiv1.ObjectName(gwNN.Name), - } - - if isSP { - SecurityPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "cors-exact", Namespace: ns}, suite.ControllerName, ancestorRef) - } - expectedResponse := http.ExpectedResponse{ Request: http.Request{ Path: "/cors-exact", @@ -209,34 +164,10 @@ func testCORS(t *testing.T, suite *suite.ConformanceTestSuite, isSP bool) { Namespace: "", } - req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http") - cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req) - if err != nil { - t.Errorf("failed to get expected response: %v", err) - } - - if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil { - t.Errorf("failed to compare request and response: %v", err) - } + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse) }) t.Run("should enable cors with wildcard matching", func(t *testing.T) { - ns := "gateway-conformance-infra" - routeNN := types.NamespacedName{Name: "http-with-cors-wildcard", Namespace: ns} - gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} - gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) - - ancestorRef := gwapiv1a2.ParentReference{ - Group: gatewayapi.GroupPtr(gwapiv1.GroupName), - Kind: gatewayapi.KindPtr(resource.KindGateway), - Namespace: gatewayapi.NamespacePtr(gwNN.Namespace), - Name: gwapiv1.ObjectName(gwNN.Name), - } - - if isSP { - SecurityPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "cors-wildcard", Namespace: ns}, suite.ControllerName, ancestorRef) - } - expectedResponse := http.ExpectedResponse{ Request: http.Request{ Path: "/cors-wildcard", @@ -270,14 +201,6 @@ func testCORS(t *testing.T, suite *suite.ConformanceTestSuite, isSP bool) { Namespace: "", } - req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http") - cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req) - if err != nil { - t.Errorf("failed to get expected response: %v", err) - } - - if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil { - t.Errorf("failed to compare request and response: %v", err) - } + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse) }) } From 3b8e17bacf6a42e894b19fb2dbba4147f6f4b784 Mon Sep 17 00:00:00 2001 From: Karol Szwaj Date: Mon, 12 May 2025 15:42:50 +0200 Subject: [PATCH 56/66] chore: add multiple gateways testdata for GatewayNamespace mode (#5972) * chore: add gatewaynamespacemode multiple gateways testdata Signed-off-by: Karol Szwaj * fix lint Signed-off-by: Karol Szwaj * Add multiple resources to infra test Signed-off-by: Karol Szwaj * Review update Signed-off-by: Karol Szwaj --------- Signed-off-by: Karol Szwaj Signed-off-by: Arko Dasgupta --- ...way-namespace-mode-infra-httproute.in.yaml | 119 ++- ...ay-namespace-mode-infra-httproute.out.yaml | 326 ++++++- .../proxy/resource_provider_test.go | 179 +++- .../deployments/gateway-namespace-mode.yaml | 443 --------- .../gateway-namespace-mode/deployment.yaml | 887 ++++++++++++++++++ .../testdata/gateway-namespace-mode/hpa.yaml | 53 ++ .../gateway-namespace-mode/service.yaml | 49 + .../serviceaccount.yaml | 25 + 8 files changed, 1618 insertions(+), 463 deletions(-) delete mode 100644 internal/infrastructure/kubernetes/proxy/testdata/deployments/gateway-namespace-mode.yaml create mode 100644 internal/infrastructure/kubernetes/proxy/testdata/gateway-namespace-mode/deployment.yaml create mode 100644 internal/infrastructure/kubernetes/proxy/testdata/gateway-namespace-mode/hpa.yaml create mode 100644 internal/infrastructure/kubernetes/proxy/testdata/gateway-namespace-mode/service.yaml create mode 100644 internal/infrastructure/kubernetes/proxy/testdata/gateway-namespace-mode/serviceaccount.yaml diff --git a/internal/gatewayapi/testdata/gateway-namespace-mode-infra-httproute.in.yaml b/internal/gatewayapi/testdata/gateway-namespace-mode-infra-httproute.in.yaml index f5bb5f15a1..3e2632f219 100644 --- a/internal/gatewayapi/testdata/gateway-namespace-mode-infra-httproute.in.yaml +++ b/internal/gatewayapi/testdata/gateway-namespace-mode-infra-httproute.in.yaml @@ -13,6 +13,34 @@ gateways: allowedRoutes: namespaces: from: All + - apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: default + name: gateway-2 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All + - apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: test-ns + name: gateway-3 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All httpRoutes: - apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute @@ -21,7 +49,7 @@ httpRoutes: name: httproute-1 spec: parentRefs: - - namespace: envoy-gateway + - namespace: default name: gateway-1 rules: - matches: @@ -29,4 +57,93 @@ httpRoutes: value: "/" backendRefs: - name: service-1 + namespace: default + port: 8080 + - apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + parentRefs: + - namespace: default + name: gateway-2 + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-2 + namespace: default + port: 8080 + - apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: test-ns + name: httproute-3 + spec: + parentRefs: + - namespace: test-ns + name: gateway-3 + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-3 + namespace: test-ns port: 8080 +services: + - apiVersion: v1 + kind: Service + metadata: + namespace: default + name: service-1 + spec: + clusterIP: 1.1.1.1 + ports: + - name: http + port: 8080 + protocol: TCP + targetPort: 8080 + - apiVersion: v1 + kind: Service + metadata: + namespace: default + name: service-2 + spec: + clusterIP: 2.2.2.2 + ports: + - name: http + port: 8080 + protocol: TCP + targetPort: 8080 + - apiVersion: v1 + kind: Service + metadata: + namespace: default + name: service-2 + spec: + clusterIP: 2.2.2.2 + ports: + - name: http + port: 8080 + protocol: TCP + targetPort: 8080 + - apiVersion: v1 + kind: Service + metadata: + namespace: test-ns + name: service-3 + spec: + clusterIP: 2.2.2.2 + ports: + - name: http + port: 8080 + protocol: TCP + targetPort: 8080 +namespaces: + - apiVersion: v1 + kind: Namespace + metadata: + name: test-ns diff --git a/internal/gatewayapi/testdata/gateway-namespace-mode-infra-httproute.out.yaml b/internal/gatewayapi/testdata/gateway-namespace-mode-infra-httproute.out.yaml index f9565bc813..8d453faf57 100644 --- a/internal/gatewayapi/testdata/gateway-namespace-mode-infra-httproute.out.yaml +++ b/internal/gatewayapi/testdata/gateway-namespace-mode-infra-httproute.out.yaml @@ -16,7 +16,7 @@ gateways: protocol: HTTP status: listeners: - - attachedRoutes: 0 + - attachedRoutes: 1 conditions: - lastTransitionTime: null message: Sending translated listener configuration to the data plane @@ -39,6 +39,192 @@ gateways: kind: HTTPRoute - group: gateway.networking.k8s.io kind: GRPCRoute +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-2 + namespace: default + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-3 + namespace: test-ns + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + parentRefs: + - name: gateway-1 + namespace: default + rules: + - backendRefs: + - name: service-1 + namespace: default + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: default +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-2 + namespace: default + spec: + parentRefs: + - name: gateway-2 + namespace: default + rules: + - backendRefs: + - name: service-2 + namespace: default + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-2 + namespace: default +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-3 + namespace: test-ns + spec: + parentRefs: + - name: gateway-3 + namespace: test-ns + rules: + - backendRefs: + - name: service-3 + namespace: test-ns + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-3 + namespace: test-ns infraIR: default/gateway-1: proxy: @@ -56,6 +242,38 @@ infraIR: gateway.envoyproxy.io/owning-gateway-namespace: default name: default/gateway-1 namespace: default + default/gateway-2: + proxy: + listeners: + - address: null + name: default/gateway-2/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-2 + gateway.envoyproxy.io/owning-gateway-namespace: default + name: default/gateway-2 + namespace: default + test-ns/gateway-3: + proxy: + listeners: + - address: null + name: test-ns/gateway-3/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-3 + gateway.envoyproxy.io/owning-gateway-namespace: test-ns + name: test-ns/gateway-3 + namespace: test-ns xdsIR: default/gateway-1: accessLog: @@ -76,6 +294,112 @@ xdsIR: escapedSlashesAction: UnescapeAndRedirect mergeSlashes: true port: 10080 + routes: + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/* + pathMatch: + distinct: false + name: "" + prefix: / + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 + default/gateway-2: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-2 + namespace: default + sectionName: http + name: default/gateway-2/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-2/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-2 + namespace: default + name: httproute/default/httproute-2/rule/0/match/0/* + pathMatch: + distinct: false + name: "" + prefix: / + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 + test-ns/gateway-3: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-3 + namespace: test-ns + sectionName: http + name: test-ns/gateway-3/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - directResponse: + statusCode: 500 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-3 + namespace: test-ns + name: httproute/test-ns/httproute-3/rule/0/match/0/* + pathMatch: + distinct: false + name: "" + prefix: / readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/infrastructure/kubernetes/proxy/resource_provider_test.go b/internal/infrastructure/kubernetes/proxy/resource_provider_test.go index 3d9b5ef316..f8165efacb 100644 --- a/internal/infrastructure/kubernetes/proxy/resource_provider_test.go +++ b/internal/infrastructure/kubernetes/proxy/resource_provider_test.go @@ -568,11 +568,6 @@ func TestDeployment(t *testing.T) { Name: ptr.To("custom-deployment-name"), }, }, - { - caseName: "gateway-namespace-mode", - infra: newTestInfraWithNamespace("ns1"), - gatewayNamespaceMode: true, - }, } for _, tc := range cases { t.Run(tc.caseName, func(t *testing.T) { @@ -618,19 +613,6 @@ func TestDeployment(t *testing.T) { tc.infra.Proxy.Config.Spec.ExtraArgs = tc.extraArgs } infraNamespace := cfg.ControllerNamespace - if tc.gatewayNamespaceMode { - deployType := egv1a1.KubernetesDeployModeType(egv1a1.KubernetesDeployModeTypeGatewayNamespace) - cfg.EnvoyGateway.Provider = &egv1a1.EnvoyGatewayProvider{ - Type: egv1a1.ProviderTypeKubernetes, - Kubernetes: &egv1a1.EnvoyGatewayKubernetesProvider{ - Deploy: &egv1a1.KubernetesDeployMode{ - Type: &deployType, - }, - }, - } - infraNamespace = tc.infra.GetProxyInfra().Namespace - } - r := NewResourceRender(infraNamespace, cfg.ControllerNamespace, cfg.DNSDomain, tc.infra.GetProxyInfra(), cfg.EnvoyGateway) dp, err := r.Deployment() require.NoError(t, err) @@ -1695,3 +1677,164 @@ func TestIPFamilyPresentInSpec(t *testing.T) { }) } } + +func TestGatewayNamespaceModeMultipleResources(t *testing.T) { + cfg, err := config.New(os.Stdout) + require.NoError(t, err) + + // Configure gateway namespace mode + deployType := egv1a1.KubernetesDeployModeType(egv1a1.KubernetesDeployModeTypeGatewayNamespace) + cfg.EnvoyGateway.Provider = &egv1a1.EnvoyGatewayProvider{ + Type: egv1a1.ProviderTypeKubernetes, + Kubernetes: &egv1a1.EnvoyGatewayKubernetesProvider{ + Deploy: &egv1a1.KubernetesDeployMode{ + Type: &deployType, + }, + }, + } + + // Create test infra with multiple namespaces + var infraList []*ir.Infra + infra1 := newTestInfraWithNamespace("namespace-1") + infra1.Proxy.Name = "namespace-1/gateway-1" + infra1.Proxy.GetProxyMetadata().Labels[gatewayapi.OwningGatewayNameLabel] = "gateway-1" + // Add HPA config to first infra + if infra1.Proxy.Config == nil { + infra1.Proxy.Config = &egv1a1.EnvoyProxy{Spec: egv1a1.EnvoyProxySpec{}} + } + if infra1.Proxy.Config.Spec.Provider == nil { + infra1.Proxy.Config.Spec.Provider = &egv1a1.EnvoyProxyProvider{} + } + infra1.Proxy.Config.Spec.Provider.Type = egv1a1.ProviderTypeKubernetes + if infra1.Proxy.Config.Spec.Provider.Kubernetes == nil { + infra1.Proxy.Config.Spec.Provider.Kubernetes = &egv1a1.EnvoyProxyKubernetesProvider{} + } + infra1.Proxy.Config.Spec.Provider.Kubernetes.EnvoyHpa = &egv1a1.KubernetesHorizontalPodAutoscalerSpec{ + MinReplicas: ptr.To[int32](1), + MaxReplicas: ptr.To[int32](3), + } + + infra2 := newTestInfraWithNamespace("namespace-2") + infra2.Proxy.Name = "namespace-2/gateway-2" + infra2.Proxy.GetProxyMetadata().Labels[gatewayapi.OwningGatewayNameLabel] = "gateway-2" + // Add HPA config to second infra + if infra2.Proxy.Config == nil { + infra2.Proxy.Config = &egv1a1.EnvoyProxy{Spec: egv1a1.EnvoyProxySpec{}} + } + if infra2.Proxy.Config.Spec.Provider == nil { + infra2.Proxy.Config.Spec.Provider = &egv1a1.EnvoyProxyProvider{} + } + infra2.Proxy.Config.Spec.Provider.Type = egv1a1.ProviderTypeKubernetes + if infra2.Proxy.Config.Spec.Provider.Kubernetes == nil { + infra2.Proxy.Config.Spec.Provider.Kubernetes = &egv1a1.EnvoyProxyKubernetesProvider{} + } + infra2.Proxy.Config.Spec.Provider.Kubernetes.EnvoyHpa = &egv1a1.KubernetesHorizontalPodAutoscalerSpec{ + MinReplicas: ptr.To[int32](1), + MaxReplicas: ptr.To[int32](3), + } + + infraList = append(infraList, infra1, infra2) + + deployments := make([]*appsv1.Deployment, 0, len(infraList)) + services := make([]*corev1.Service, 0, len(infraList)) + serviceAccounts := make([]*corev1.ServiceAccount, 0, len(infraList)) + hpas := make([]*autoscalingv2.HorizontalPodAutoscaler, 0, len(infraList)) + + for _, infra := range infraList { + namespace := infra.GetProxyInfra().Namespace + r := NewResourceRender(namespace, cfg.ControllerNamespace, cfg.DNSDomain, + infra.GetProxyInfra(), cfg.EnvoyGateway) + + dp, err := r.Deployment() + require.NoError(t, err) + deployments = append(deployments, dp) + + svc, err := r.Service() + require.NoError(t, err) + services = append(services, svc) + + sa, err := r.ServiceAccount() + require.NoError(t, err) + serviceAccounts = append(serviceAccounts, sa) + + hpa, err := r.HorizontalPodAutoscaler() + require.NoError(t, err) + hpas = append(hpas, hpa) + + } + + // Verify correct number of resources + require.Len(t, deployments, len(infraList)) + require.Len(t, services, len(infraList)) + require.Len(t, serviceAccounts, len(infraList)) + require.Len(t, hpas, len(infraList)) + + if test.OverrideTestData() { + deploymentInterfaces := make([]any, len(deployments)) + for i, dp := range deployments { + deploymentInterfaces[i] = dp + } + + err := writeTestDataToFile("testdata/gateway-namespace-mode/deployment.yaml", deploymentInterfaces) + require.NoError(t, err) + + serviceInterfaces := make([]any, len(services)) + for i, svc := range services { + serviceInterfaces[i] = svc + } + err = writeTestDataToFile("testdata/gateway-namespace-mode/service.yaml", serviceInterfaces) + require.NoError(t, err) + + saInterfaces := make([]any, len(serviceAccounts)) + for i, sa := range serviceAccounts { + saInterfaces[i] = sa + } + err = writeTestDataToFile("testdata/gateway-namespace-mode/serviceaccount.yaml", saInterfaces) + require.NoError(t, err) + + hpaInterfaces := make([]any, len(hpas)) + for i, hpa := range hpas { + hpaInterfaces[i] = hpa + } + err = writeTestDataToFile("testdata/gateway-namespace-mode/hpa.yaml", hpaInterfaces) + require.NoError(t, err) + + return + } + + for i, infra := range infraList { + expectedNamespace := infra.GetProxyInfra().Namespace + expectedName := ExpectedResourceHashedName(infra.GetProxyInfra().Name) + + require.Equal(t, expectedNamespace, deployments[i].Namespace) + require.Equal(t, expectedName, deployments[i].Name) + + require.Equal(t, expectedNamespace, services[i].Namespace) + require.Equal(t, expectedName, services[i].Name) + + require.Equal(t, expectedNamespace, serviceAccounts[i].Namespace) + require.Equal(t, expectedName, serviceAccounts[i].Name) + + if i < len(hpas) { + require.Equal(t, expectedNamespace, hpas[i].Namespace) + require.Equal(t, expectedName, hpas[i].Name) + require.Equal(t, expectedName, hpas[i].Spec.ScaleTargetRef.Name) + } + } +} + +func writeTestDataToFile(filename string, resources []any) error { + var combinedYAML []byte + for i, resource := range resources { + resourceYAML, err := yaml.Marshal(resource) + if err != nil { + return err + } + if i > 0 { + combinedYAML = append(combinedYAML, []byte("---\n")...) + } + combinedYAML = append(combinedYAML, resourceYAML...) + } + + return os.WriteFile(filename, combinedYAML, 0o600) +} diff --git a/internal/infrastructure/kubernetes/proxy/testdata/deployments/gateway-namespace-mode.yaml b/internal/infrastructure/kubernetes/proxy/testdata/deployments/gateway-namespace-mode.yaml deleted file mode 100644 index d659860536..0000000000 --- a/internal/infrastructure/kubernetes/proxy/testdata/deployments/gateway-namespace-mode.yaml +++ /dev/null @@ -1,443 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - creationTimestamp: null - labels: - app.kubernetes.io/component: proxy - app.kubernetes.io/managed-by: envoy-gateway - app.kubernetes.io/name: envoy - gateway.envoyproxy.io/owning-gateway-name: default - gateway.envoyproxy.io/owning-gateway-namespace: ns1 - name: envoy-default-37a8eec1 - namespace: ns1 -spec: - progressDeadlineSeconds: 600 - revisionHistoryLimit: 10 - selector: - matchLabels: - app.kubernetes.io/component: proxy - app.kubernetes.io/managed-by: envoy-gateway - app.kubernetes.io/name: envoy - gateway.envoyproxy.io/owning-gateway-name: default - gateway.envoyproxy.io/owning-gateway-namespace: ns1 - strategy: - type: RollingUpdate - template: - metadata: - annotations: - prometheus.io/path: /stats/prometheus - prometheus.io/port: "19001" - prometheus.io/scrape: "true" - creationTimestamp: null - labels: - app.kubernetes.io/component: proxy - app.kubernetes.io/managed-by: envoy-gateway - app.kubernetes.io/name: envoy - gateway.envoyproxy.io/owning-gateway-name: default - gateway.envoyproxy.io/owning-gateway-namespace: ns1 - spec: - containers: - - args: - - --service-cluster default - - --service-node $(ENVOY_POD_NAME) - - | - --config-yaml admin: - access_log: - - name: envoy.access_loggers.file - typed_config: - "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog - path: /dev/null - address: - socket_address: - address: 127.0.0.1 - port_value: 19000 - cluster_manager: - local_cluster_name: local_cluster - node: - locality: - zone: "$(ENVOY_SERVICE_ZONE)" - layered_runtime: - layers: - - name: global_config - static_layer: - envoy.restart_features.use_eds_cache_for_ads: true - re2.max_program_size.error_level: 4294967295 - re2.max_program_size.warn_level: 1000 - dynamic_resources: - ads_config: - api_type: DELTA_GRPC - transport_api_version: V3 - grpc_services: - - envoy_grpc: - cluster_name: xds_cluster - set_node_on_first_message_only: true - lds_config: - ads: {} - resource_api_version: V3 - cds_config: - ads: {} - resource_api_version: V3 - static_resources: - listeners: - - name: envoy-gateway-proxy-stats-0.0.0.0-19001 - address: - socket_address: - address: '0.0.0.0' - port_value: 19001 - protocol: TCP - bypass_overload_manager: true - filter_chains: - - 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: eg-stats-http - normalize_path: true - route_config: - name: local_route - virtual_hosts: - - name: prometheus_stats - domains: - - "*" - routes: - - match: - path: /stats/prometheus - headers: - - name: ":method" - string_match: - exact: GET - route: - cluster: prometheus_stats - http_filters: - - name: envoy.filters.http.router - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router - clusters: - - name: prometheus_stats - connect_timeout: 0.250s - type: STATIC - lb_policy: ROUND_ROBIN - load_assignment: - cluster_name: prometheus_stats - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: 127.0.0.1 - port_value: 19000 - - connect_timeout: 10s - lb_policy: ROUND_ROBIN - load_assignment: - cluster_name: local_cluster - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: 127.0.0.1 - port_value: 10080 - load_balancing_weight: 1 - load_balancing_weight: 1 - locality: - zone: "$(ENVOY_SERVICE_ZONE)" - name: local_cluster - type: STATIC - - connect_timeout: 10s - load_assignment: - cluster_name: xds_cluster - endpoints: - - load_balancing_weight: 1 - lb_endpoints: - - load_balancing_weight: 1 - endpoint: - address: - socket_address: - address: envoy-gateway.envoy-gateway-system.svc.cluster.local - port_value: 18000 - typed_extension_protocol_options: - envoy.extensions.upstreams.http.v3.HttpProtocolOptions: - "@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions" - explicit_http_config: - http2_protocol_options: - connection_keepalive: - interval: 30s - timeout: 5s - http_filters: - - name: envoy.filters.http.credential_injector - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.credential_injector.v3.CredentialInjector - credential: - name: envoy.http.injected_credentials.generic - typed_config: - "@type": type.googleapis.com/envoy.extensions.http.injected_credentials.generic.v3.Generic - credential: - name: jwt-sa-bearer - overwrite: true - - name: envoy.extensions.filters.http.upstream_codec.v3.UpstreamCodec - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.upstream_codec.v3.UpstreamCodec - name: xds_cluster - type: STRICT_DNS - transport_socket: - name: envoy.transport_sockets.tls - typed_config: - "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext - common_tls_context: - tls_params: - tls_maximum_protocol_version: TLSv1_3 - validation_context_sds_secret_config: - name: xds_trusted_ca - sds_config: - path_config_source: - path: /sds/xds-trusted-ca.json - resource_api_version: V3 - - name: wasm_cluster - type: STRICT_DNS - connect_timeout: 10s - load_assignment: - cluster_name: wasm_cluster - endpoints: - - load_balancing_weight: 1 - lb_endpoints: - - load_balancing_weight: 1 - endpoint: - address: - socket_address: - address: envoy-gateway - port_value: 18002 - typed_extension_protocol_options: - envoy.extensions.upstreams.http.v3.HttpProtocolOptions: - "@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions" - explicit_http_config: - http2_protocol_options: {} - http_filters: - - name: envoy.filters.http.credential_injector - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.credential_injector.v3.CredentialInjector - credential: - name: envoy.http.injected_credentials.generic - typed_config: - "@type": type.googleapis.com/envoy.extensions.http.injected_credentials.generic.v3.Generic - credential: - name: jwt-sa-bearer - overwrite: true - - name: envoy.extensions.filters.http.upstream_codec.v3.UpstreamCodec - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.upstream_codec.v3.UpstreamCodec - transport_socket: - name: envoy.transport_sockets.tls - typed_config: - "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext - common_tls_context: - tls_params: - tls_maximum_protocol_version: TLSv1_3 - validation_context_sds_secret_config: - name: xds_trusted_ca - sds_config: - path_config_source: - path: /sds/xds-trusted-ca.json - resource_api_version: V3 - secrets: - - name: jwt-sa-bearer - generic_secret: - secret: - filename: "/var/run/secrets/token/sa-token" - overload_manager: - refresh_interval: 0.25s - resource_monitors: - - name: "envoy.resource_monitors.global_downstream_max_connections" - typed_config: - "@type": type.googleapis.com/envoy.extensions.resource_monitors.downstream_connections.v3.DownstreamConnectionsConfig - max_active_downstream_connections: 50000 - - --log-level warn - - --cpuset-threads - - --drain-strategy immediate - - --drain-time-s 60 - command: - - envoy - env: - - name: ENVOY_GATEWAY_NAMESPACE - value: envoy-gateway-system - - name: ENVOY_SERVICE_ZONE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - - name: ENVOY_POD_NAME - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-v1.34.0 - imagePullPolicy: IfNotPresent - lifecycle: - preStop: - httpGet: - path: /shutdown/ready - port: 19002 - scheme: HTTP - livenessProbe: - failureThreshold: 3 - httpGet: - path: /ready - port: 19003 - scheme: HTTP - periodSeconds: 10 - successThreshold: 1 - timeoutSeconds: 1 - name: envoy - ports: - - containerPort: 19001 - name: metrics - protocol: TCP - - containerPort: 19003 - name: readiness - protocol: TCP - readinessProbe: - failureThreshold: 1 - httpGet: - path: /ready - port: 19003 - scheme: HTTP - periodSeconds: 5 - successThreshold: 1 - timeoutSeconds: 1 - resources: - requests: - cpu: 100m - memory: 512Mi - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - privileged: false - runAsGroup: 65532 - runAsNonRoot: true - runAsUser: 65532 - seccompProfile: - type: RuntimeDefault - startupProbe: - failureThreshold: 30 - httpGet: - path: /ready - port: 19003 - scheme: HTTP - periodSeconds: 10 - successThreshold: 1 - timeoutSeconds: 1 - terminationMessagePath: /dev/termination-log - terminationMessagePolicy: File - volumeMounts: - - mountPath: /certs - name: certs - readOnly: true - - mountPath: /sds - name: sds - - mountPath: /var/run/secrets/token - name: sa-token - readOnly: true - - args: - - envoy - - shutdown-manager - command: - - envoy-gateway - env: - - name: ENVOY_GATEWAY_NAMESPACE - value: envoy-gateway-system - - name: ENVOY_SERVICE_ZONE - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.annotations['topology.kubernetes.io/zone'] - - name: ENVOY_POD_NAME - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.name - image: docker.io/envoyproxy/gateway-dev:latest - imagePullPolicy: IfNotPresent - lifecycle: - preStop: - exec: - command: - - envoy-gateway - - envoy - - shutdown - livenessProbe: - failureThreshold: 3 - httpGet: - path: /healthz - port: 19002 - scheme: HTTP - periodSeconds: 10 - successThreshold: 1 - timeoutSeconds: 1 - name: shutdown-manager - readinessProbe: - failureThreshold: 3 - httpGet: - path: /healthz - port: 19002 - scheme: HTTP - periodSeconds: 10 - successThreshold: 1 - timeoutSeconds: 1 - resources: - requests: - cpu: 10m - memory: 32Mi - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - privileged: false - runAsGroup: 65532 - runAsNonRoot: true - runAsUser: 65532 - seccompProfile: - type: RuntimeDefault - startupProbe: - failureThreshold: 30 - httpGet: - path: /healthz - port: 19002 - scheme: HTTP - periodSeconds: 10 - successThreshold: 1 - timeoutSeconds: 1 - terminationMessagePath: /dev/termination-log - terminationMessagePolicy: File - dnsPolicy: ClusterFirst - restartPolicy: Always - schedulerName: default-scheduler - serviceAccountName: envoy-default-37a8eec1 - terminationGracePeriodSeconds: 360 - volumes: - - name: sa-token - projected: - defaultMode: 420 - sources: - - serviceAccountToken: - audience: envoy-gateway.envoy-gateway-system.svc.cluster.local - expirationSeconds: 3600 - path: sa-token - - configMap: - defaultMode: 420 - items: - - key: ca.crt - path: ca.crt - name: envoy-default-37a8eec1 - optional: false - name: certs - - configMap: - defaultMode: 420 - items: - - key: xds-trusted-ca.json - path: xds-trusted-ca.json - name: envoy-default-37a8eec1 - optional: false - name: sds -status: {} diff --git a/internal/infrastructure/kubernetes/proxy/testdata/gateway-namespace-mode/deployment.yaml b/internal/infrastructure/kubernetes/proxy/testdata/gateway-namespace-mode/deployment.yaml new file mode 100644 index 0000000000..ba240a1208 --- /dev/null +++ b/internal/infrastructure/kubernetes/proxy/testdata/gateway-namespace-mode/deployment.yaml @@ -0,0 +1,887 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + app.kubernetes.io/component: proxy + app.kubernetes.io/managed-by: envoy-gateway + app.kubernetes.io/name: envoy + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: namespace-1 + name: envoy-namespace-1-gateway-1-e2117e41 + namespace: namespace-1 +spec: + progressDeadlineSeconds: 600 + revisionHistoryLimit: 10 + selector: + matchLabels: + app.kubernetes.io/component: proxy + app.kubernetes.io/managed-by: envoy-gateway + app.kubernetes.io/name: envoy + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: namespace-1 + strategy: + type: RollingUpdate + template: + metadata: + annotations: + prometheus.io/path: /stats/prometheus + prometheus.io/port: "19001" + prometheus.io/scrape: "true" + creationTimestamp: null + labels: + app.kubernetes.io/component: proxy + app.kubernetes.io/managed-by: envoy-gateway + app.kubernetes.io/name: envoy + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: namespace-1 + spec: + containers: + - args: + - --service-cluster namespace-1/gateway-1 + - --service-node $(ENVOY_POD_NAME) + - | + --config-yaml admin: + access_log: + - name: envoy.access_loggers.file + typed_config: + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: /dev/null + address: + socket_address: + address: 127.0.0.1 + port_value: 19000 + cluster_manager: + local_cluster_name: local_cluster + node: + locality: + zone: "$(ENVOY_SERVICE_ZONE)" + layered_runtime: + layers: + - name: global_config + static_layer: + envoy.restart_features.use_eds_cache_for_ads: true + re2.max_program_size.error_level: 4294967295 + re2.max_program_size.warn_level: 1000 + dynamic_resources: + ads_config: + api_type: DELTA_GRPC + transport_api_version: V3 + grpc_services: + - envoy_grpc: + cluster_name: xds_cluster + set_node_on_first_message_only: true + lds_config: + ads: {} + resource_api_version: V3 + cds_config: + ads: {} + resource_api_version: V3 + static_resources: + listeners: + - name: envoy-gateway-proxy-stats-0.0.0.0-19001 + address: + socket_address: + address: '0.0.0.0' + port_value: 19001 + protocol: TCP + bypass_overload_manager: true + filter_chains: + - 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: eg-stats-http + normalize_path: true + route_config: + name: local_route + virtual_hosts: + - name: prometheus_stats + domains: + - "*" + routes: + - match: + path: /stats/prometheus + headers: + - name: ":method" + string_match: + exact: GET + route: + cluster: prometheus_stats + http_filters: + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + clusters: + - name: prometheus_stats + connect_timeout: 0.250s + type: STATIC + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: prometheus_stats + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 19000 + - connect_timeout: 10s + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: local_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 10080 + load_balancing_weight: 1 + load_balancing_weight: 1 + locality: + zone: "$(ENVOY_SERVICE_ZONE)" + name: local_cluster + type: STATIC + - connect_timeout: 10s + load_assignment: + cluster_name: xds_cluster + endpoints: + - load_balancing_weight: 1 + lb_endpoints: + - load_balancing_weight: 1 + endpoint: + address: + socket_address: + address: envoy-gateway.envoy-gateway-system.svc.cluster.local + port_value: 18000 + typed_extension_protocol_options: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + "@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions" + explicit_http_config: + http2_protocol_options: + connection_keepalive: + interval: 30s + timeout: 5s + http_filters: + - name: envoy.filters.http.credential_injector + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.credential_injector.v3.CredentialInjector + credential: + name: envoy.http.injected_credentials.generic + typed_config: + "@type": type.googleapis.com/envoy.extensions.http.injected_credentials.generic.v3.Generic + credential: + name: jwt-sa-bearer + overwrite: true + - name: envoy.extensions.filters.http.upstream_codec.v3.UpstreamCodec + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.upstream_codec.v3.UpstreamCodec + name: xds_cluster + type: STRICT_DNS + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + common_tls_context: + tls_params: + tls_maximum_protocol_version: TLSv1_3 + validation_context_sds_secret_config: + name: xds_trusted_ca + sds_config: + path_config_source: + path: /sds/xds-trusted-ca.json + resource_api_version: V3 + - name: wasm_cluster + type: STRICT_DNS + connect_timeout: 10s + load_assignment: + cluster_name: wasm_cluster + endpoints: + - load_balancing_weight: 1 + lb_endpoints: + - load_balancing_weight: 1 + endpoint: + address: + socket_address: + address: envoy-gateway + port_value: 18002 + typed_extension_protocol_options: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + "@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions" + explicit_http_config: + http2_protocol_options: {} + http_filters: + - name: envoy.filters.http.credential_injector + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.credential_injector.v3.CredentialInjector + credential: + name: envoy.http.injected_credentials.generic + typed_config: + "@type": type.googleapis.com/envoy.extensions.http.injected_credentials.generic.v3.Generic + credential: + name: jwt-sa-bearer + overwrite: true + - name: envoy.extensions.filters.http.upstream_codec.v3.UpstreamCodec + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.upstream_codec.v3.UpstreamCodec + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + common_tls_context: + tls_params: + tls_maximum_protocol_version: TLSv1_3 + validation_context_sds_secret_config: + name: xds_trusted_ca + sds_config: + path_config_source: + path: /sds/xds-trusted-ca.json + resource_api_version: V3 + secrets: + - name: jwt-sa-bearer + generic_secret: + secret: + filename: "/var/run/secrets/token/sa-token" + overload_manager: + refresh_interval: 0.25s + resource_monitors: + - name: "envoy.resource_monitors.global_downstream_max_connections" + typed_config: + "@type": type.googleapis.com/envoy.extensions.resource_monitors.downstream_connections.v3.DownstreamConnectionsConfig + max_active_downstream_connections: 50000 + - --log-level warn + - --cpuset-threads + - --drain-strategy immediate + - --drain-time-s 60 + command: + - envoy + env: + - name: ENVOY_GATEWAY_NAMESPACE + value: envoy-gateway-system + - name: ENVOY_SERVICE_ZONE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] + - name: ENVOY_POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + image: docker.io/envoyproxy/envoy:distroless-dev + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + httpGet: + path: /shutdown/ready + port: 19002 + scheme: HTTP + livenessProbe: + failureThreshold: 3 + httpGet: + path: /ready + port: 19003 + scheme: HTTP + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + name: envoy + ports: + - containerPort: 19001 + name: metrics + protocol: TCP + - containerPort: 19003 + name: readiness + protocol: TCP + readinessProbe: + failureThreshold: 1 + httpGet: + path: /ready + port: 19003 + scheme: HTTP + periodSeconds: 5 + successThreshold: 1 + timeoutSeconds: 1 + resources: + requests: + cpu: 100m + memory: 512Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + runAsGroup: 65532 + runAsNonRoot: true + runAsUser: 65532 + seccompProfile: + type: RuntimeDefault + startupProbe: + failureThreshold: 30 + httpGet: + path: /ready + port: 19003 + scheme: HTTP + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /certs + name: certs + readOnly: true + - mountPath: /sds + name: sds + - mountPath: /var/run/secrets/token + name: sa-token + readOnly: true + - args: + - envoy + - shutdown-manager + command: + - envoy-gateway + env: + - name: ENVOY_GATEWAY_NAMESPACE + value: envoy-gateway-system + - name: ENVOY_SERVICE_ZONE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] + - name: ENVOY_POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + image: docker.io/envoyproxy/gateway-dev:latest + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + exec: + command: + - envoy-gateway + - envoy + - shutdown + livenessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 19002 + scheme: HTTP + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + name: shutdown-manager + readinessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 19002 + scheme: HTTP + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + resources: + requests: + cpu: 10m + memory: 32Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + runAsGroup: 65532 + runAsNonRoot: true + runAsUser: 65532 + seccompProfile: + type: RuntimeDefault + startupProbe: + failureThreshold: 30 + httpGet: + path: /healthz + port: 19002 + scheme: HTTP + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + serviceAccountName: envoy-namespace-1-gateway-1-e2117e41 + terminationGracePeriodSeconds: 360 + volumes: + - name: sa-token + projected: + defaultMode: 420 + sources: + - serviceAccountToken: + audience: envoy-gateway.envoy-gateway-system.svc.cluster.local + expirationSeconds: 3600 + path: sa-token + - configMap: + defaultMode: 420 + items: + - key: ca.crt + path: ca.crt + name: envoy-namespace-1-gateway-1-e2117e41 + optional: false + name: certs + - configMap: + defaultMode: 420 + items: + - key: xds-trusted-ca.json + path: xds-trusted-ca.json + name: envoy-namespace-1-gateway-1-e2117e41 + optional: false + name: sds +status: {} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + creationTimestamp: null + labels: + app.kubernetes.io/component: proxy + app.kubernetes.io/managed-by: envoy-gateway + app.kubernetes.io/name: envoy + gateway.envoyproxy.io/owning-gateway-name: gateway-2 + gateway.envoyproxy.io/owning-gateway-namespace: namespace-2 + name: envoy-namespace-2-gateway-2-107e8cb2 + namespace: namespace-2 +spec: + progressDeadlineSeconds: 600 + revisionHistoryLimit: 10 + selector: + matchLabels: + app.kubernetes.io/component: proxy + app.kubernetes.io/managed-by: envoy-gateway + app.kubernetes.io/name: envoy + gateway.envoyproxy.io/owning-gateway-name: gateway-2 + gateway.envoyproxy.io/owning-gateway-namespace: namespace-2 + strategy: + type: RollingUpdate + template: + metadata: + annotations: + prometheus.io/path: /stats/prometheus + prometheus.io/port: "19001" + prometheus.io/scrape: "true" + creationTimestamp: null + labels: + app.kubernetes.io/component: proxy + app.kubernetes.io/managed-by: envoy-gateway + app.kubernetes.io/name: envoy + gateway.envoyproxy.io/owning-gateway-name: gateway-2 + gateway.envoyproxy.io/owning-gateway-namespace: namespace-2 + spec: + containers: + - args: + - --service-cluster namespace-2/gateway-2 + - --service-node $(ENVOY_POD_NAME) + - | + --config-yaml admin: + access_log: + - name: envoy.access_loggers.file + typed_config: + "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog + path: /dev/null + address: + socket_address: + address: 127.0.0.1 + port_value: 19000 + cluster_manager: + local_cluster_name: local_cluster + node: + locality: + zone: "$(ENVOY_SERVICE_ZONE)" + layered_runtime: + layers: + - name: global_config + static_layer: + envoy.restart_features.use_eds_cache_for_ads: true + re2.max_program_size.error_level: 4294967295 + re2.max_program_size.warn_level: 1000 + dynamic_resources: + ads_config: + api_type: DELTA_GRPC + transport_api_version: V3 + grpc_services: + - envoy_grpc: + cluster_name: xds_cluster + set_node_on_first_message_only: true + lds_config: + ads: {} + resource_api_version: V3 + cds_config: + ads: {} + resource_api_version: V3 + static_resources: + listeners: + - name: envoy-gateway-proxy-stats-0.0.0.0-19001 + address: + socket_address: + address: '0.0.0.0' + port_value: 19001 + protocol: TCP + bypass_overload_manager: true + filter_chains: + - 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: eg-stats-http + normalize_path: true + route_config: + name: local_route + virtual_hosts: + - name: prometheus_stats + domains: + - "*" + routes: + - match: + path: /stats/prometheus + headers: + - name: ":method" + string_match: + exact: GET + route: + cluster: prometheus_stats + http_filters: + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + clusters: + - name: prometheus_stats + connect_timeout: 0.250s + type: STATIC + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: prometheus_stats + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 19000 + - connect_timeout: 10s + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: local_cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 10080 + load_balancing_weight: 1 + load_balancing_weight: 1 + locality: + zone: "$(ENVOY_SERVICE_ZONE)" + name: local_cluster + type: STATIC + - connect_timeout: 10s + load_assignment: + cluster_name: xds_cluster + endpoints: + - load_balancing_weight: 1 + lb_endpoints: + - load_balancing_weight: 1 + endpoint: + address: + socket_address: + address: envoy-gateway.envoy-gateway-system.svc.cluster.local + port_value: 18000 + typed_extension_protocol_options: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + "@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions" + explicit_http_config: + http2_protocol_options: + connection_keepalive: + interval: 30s + timeout: 5s + http_filters: + - name: envoy.filters.http.credential_injector + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.credential_injector.v3.CredentialInjector + credential: + name: envoy.http.injected_credentials.generic + typed_config: + "@type": type.googleapis.com/envoy.extensions.http.injected_credentials.generic.v3.Generic + credential: + name: jwt-sa-bearer + overwrite: true + - name: envoy.extensions.filters.http.upstream_codec.v3.UpstreamCodec + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.upstream_codec.v3.UpstreamCodec + name: xds_cluster + type: STRICT_DNS + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + common_tls_context: + tls_params: + tls_maximum_protocol_version: TLSv1_3 + validation_context_sds_secret_config: + name: xds_trusted_ca + sds_config: + path_config_source: + path: /sds/xds-trusted-ca.json + resource_api_version: V3 + - name: wasm_cluster + type: STRICT_DNS + connect_timeout: 10s + load_assignment: + cluster_name: wasm_cluster + endpoints: + - load_balancing_weight: 1 + lb_endpoints: + - load_balancing_weight: 1 + endpoint: + address: + socket_address: + address: envoy-gateway + port_value: 18002 + typed_extension_protocol_options: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + "@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions" + explicit_http_config: + http2_protocol_options: {} + http_filters: + - name: envoy.filters.http.credential_injector + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.credential_injector.v3.CredentialInjector + credential: + name: envoy.http.injected_credentials.generic + typed_config: + "@type": type.googleapis.com/envoy.extensions.http.injected_credentials.generic.v3.Generic + credential: + name: jwt-sa-bearer + overwrite: true + - name: envoy.extensions.filters.http.upstream_codec.v3.UpstreamCodec + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.upstream_codec.v3.UpstreamCodec + transport_socket: + name: envoy.transport_sockets.tls + typed_config: + "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + common_tls_context: + tls_params: + tls_maximum_protocol_version: TLSv1_3 + validation_context_sds_secret_config: + name: xds_trusted_ca + sds_config: + path_config_source: + path: /sds/xds-trusted-ca.json + resource_api_version: V3 + secrets: + - name: jwt-sa-bearer + generic_secret: + secret: + filename: "/var/run/secrets/token/sa-token" + overload_manager: + refresh_interval: 0.25s + resource_monitors: + - name: "envoy.resource_monitors.global_downstream_max_connections" + typed_config: + "@type": type.googleapis.com/envoy.extensions.resource_monitors.downstream_connections.v3.DownstreamConnectionsConfig + max_active_downstream_connections: 50000 + - --log-level warn + - --cpuset-threads + - --drain-strategy immediate + - --drain-time-s 60 + command: + - envoy + env: + - name: ENVOY_GATEWAY_NAMESPACE + value: envoy-gateway-system + - name: ENVOY_SERVICE_ZONE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] + - name: ENVOY_POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + image: docker.io/envoyproxy/envoy:distroless-dev + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + httpGet: + path: /shutdown/ready + port: 19002 + scheme: HTTP + livenessProbe: + failureThreshold: 3 + httpGet: + path: /ready + port: 19003 + scheme: HTTP + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + name: envoy + ports: + - containerPort: 19001 + name: metrics + protocol: TCP + - containerPort: 19003 + name: readiness + protocol: TCP + readinessProbe: + failureThreshold: 1 + httpGet: + path: /ready + port: 19003 + scheme: HTTP + periodSeconds: 5 + successThreshold: 1 + timeoutSeconds: 1 + resources: + requests: + cpu: 100m + memory: 512Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + runAsGroup: 65532 + runAsNonRoot: true + runAsUser: 65532 + seccompProfile: + type: RuntimeDefault + startupProbe: + failureThreshold: 30 + httpGet: + path: /ready + port: 19003 + scheme: HTTP + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /certs + name: certs + readOnly: true + - mountPath: /sds + name: sds + - mountPath: /var/run/secrets/token + name: sa-token + readOnly: true + - args: + - envoy + - shutdown-manager + command: + - envoy-gateway + env: + - name: ENVOY_GATEWAY_NAMESPACE + value: envoy-gateway-system + - name: ENVOY_SERVICE_ZONE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.annotations['topology.kubernetes.io/zone'] + - name: ENVOY_POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + image: docker.io/envoyproxy/gateway-dev:latest + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + exec: + command: + - envoy-gateway + - envoy + - shutdown + livenessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 19002 + scheme: HTTP + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + name: shutdown-manager + readinessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 19002 + scheme: HTTP + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + resources: + requests: + cpu: 10m + memory: 32Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + runAsGroup: 65532 + runAsNonRoot: true + runAsUser: 65532 + seccompProfile: + type: RuntimeDefault + startupProbe: + failureThreshold: 30 + httpGet: + path: /healthz + port: 19002 + scheme: HTTP + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + serviceAccountName: envoy-namespace-2-gateway-2-107e8cb2 + terminationGracePeriodSeconds: 360 + volumes: + - name: sa-token + projected: + defaultMode: 420 + sources: + - serviceAccountToken: + audience: envoy-gateway.envoy-gateway-system.svc.cluster.local + expirationSeconds: 3600 + path: sa-token + - configMap: + defaultMode: 420 + items: + - key: ca.crt + path: ca.crt + name: envoy-namespace-2-gateway-2-107e8cb2 + optional: false + name: certs + - configMap: + defaultMode: 420 + items: + - key: xds-trusted-ca.json + path: xds-trusted-ca.json + name: envoy-namespace-2-gateway-2-107e8cb2 + optional: false + name: sds +status: {} diff --git a/internal/infrastructure/kubernetes/proxy/testdata/gateway-namespace-mode/hpa.yaml b/internal/infrastructure/kubernetes/proxy/testdata/gateway-namespace-mode/hpa.yaml new file mode 100644 index 0000000000..0724e0c089 --- /dev/null +++ b/internal/infrastructure/kubernetes/proxy/testdata/gateway-namespace-mode/hpa.yaml @@ -0,0 +1,53 @@ +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + creationTimestamp: null + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: namespace-1 + name: envoy-namespace-1-gateway-1-e2117e41 + namespace: namespace-1 +spec: + maxReplicas: 3 + metrics: + - resource: + name: cpu + target: + averageUtilization: 80 + type: Utilization + type: Resource + minReplicas: 1 + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: envoy-namespace-1-gateway-1-e2117e41 +status: + currentMetrics: null + desiredReplicas: 0 +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + creationTimestamp: null + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-2 + gateway.envoyproxy.io/owning-gateway-namespace: namespace-2 + name: envoy-namespace-2-gateway-2-107e8cb2 + namespace: namespace-2 +spec: + maxReplicas: 3 + metrics: + - resource: + name: cpu + target: + averageUtilization: 80 + type: Utilization + type: Resource + minReplicas: 1 + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: envoy-namespace-2-gateway-2-107e8cb2 +status: + currentMetrics: null + desiredReplicas: 0 diff --git a/internal/infrastructure/kubernetes/proxy/testdata/gateway-namespace-mode/service.yaml b/internal/infrastructure/kubernetes/proxy/testdata/gateway-namespace-mode/service.yaml new file mode 100644 index 0000000000..50e94c01d2 --- /dev/null +++ b/internal/infrastructure/kubernetes/proxy/testdata/gateway-namespace-mode/service.yaml @@ -0,0 +1,49 @@ +apiVersion: v1 +kind: Service +metadata: + creationTimestamp: null + labels: + app.kubernetes.io/component: proxy + app.kubernetes.io/managed-by: envoy-gateway + app.kubernetes.io/name: envoy + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: namespace-1 + name: envoy-namespace-1-gateway-1-e2117e41 + namespace: namespace-1 +spec: + externalTrafficPolicy: Local + selector: + app.kubernetes.io/component: proxy + app.kubernetes.io/managed-by: envoy-gateway + app.kubernetes.io/name: envoy + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: namespace-1 + sessionAffinity: None + type: LoadBalancer +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: Service +metadata: + creationTimestamp: null + labels: + app.kubernetes.io/component: proxy + app.kubernetes.io/managed-by: envoy-gateway + app.kubernetes.io/name: envoy + gateway.envoyproxy.io/owning-gateway-name: gateway-2 + gateway.envoyproxy.io/owning-gateway-namespace: namespace-2 + name: envoy-namespace-2-gateway-2-107e8cb2 + namespace: namespace-2 +spec: + externalTrafficPolicy: Local + selector: + app.kubernetes.io/component: proxy + app.kubernetes.io/managed-by: envoy-gateway + app.kubernetes.io/name: envoy + gateway.envoyproxy.io/owning-gateway-name: gateway-2 + gateway.envoyproxy.io/owning-gateway-namespace: namespace-2 + sessionAffinity: None + type: LoadBalancer +status: + loadBalancer: {} diff --git a/internal/infrastructure/kubernetes/proxy/testdata/gateway-namespace-mode/serviceaccount.yaml b/internal/infrastructure/kubernetes/proxy/testdata/gateway-namespace-mode/serviceaccount.yaml new file mode 100644 index 0000000000..8c8b81a6b4 --- /dev/null +++ b/internal/infrastructure/kubernetes/proxy/testdata/gateway-namespace-mode/serviceaccount.yaml @@ -0,0 +1,25 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + creationTimestamp: null + labels: + app.kubernetes.io/component: proxy + app.kubernetes.io/managed-by: envoy-gateway + app.kubernetes.io/name: envoy + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: namespace-1 + name: envoy-namespace-1-gateway-1-e2117e41 + namespace: namespace-1 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + creationTimestamp: null + labels: + app.kubernetes.io/component: proxy + app.kubernetes.io/managed-by: envoy-gateway + app.kubernetes.io/name: envoy + gateway.envoyproxy.io/owning-gateway-name: gateway-2 + gateway.envoyproxy.io/owning-gateway-namespace: namespace-2 + name: envoy-namespace-2-gateway-2-107e8cb2 + namespace: namespace-2 From dcc8329483d7bf994c99dd07b2de09fb98a359cc Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Mon, 12 May 2025 16:27:30 -0700 Subject: [PATCH 57/66] feat: adds support for extension server in standalone mode (#5984) Signed-off-by: Takeshi Yoneda Signed-off-by: Arko Dasgupta --- internal/cmd/server.go | 6 ++---- internal/extension/registry/extension_manager.go | 15 +++++++++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/internal/cmd/server.go b/internal/cmd/server.go index 6a39bcc24c..7f6a37e2f2 100644 --- a/internal/cmd/server.go +++ b/internal/cmd/server.go @@ -161,10 +161,8 @@ func startRunners(ctx context.Context, cfg *config.Server) (err error) { // Setup the Extension Manager var extMgr types.Manager - if cfg.EnvoyGateway.Provider.Type == egv1a1.ProviderTypeKubernetes { - if extMgr, err = extensionregistry.NewManager(cfg); err != nil { - return err - } + if extMgr, err = extensionregistry.NewManager(cfg, cfg.EnvoyGateway.Provider.Type == egv1a1.ProviderTypeKubernetes); err != nil { + return err } runners := []struct { diff --git a/internal/extension/registry/extension_manager.go b/internal/extension/registry/extension_manager.go index aa041bd93b..0506e71d59 100644 --- a/internal/extension/registry/extension_manager.go +++ b/internal/extension/registry/extension_manager.go @@ -59,10 +59,14 @@ type Manager struct { } // NewManager returns a new Manager -func NewManager(cfg *config.Server) (extTypes.Manager, error) { - cli, err := k8scli.New(k8sclicfg.GetConfigOrDie(), k8scli.Options{Scheme: envoygateway.GetScheme()}) - if err != nil { - return nil, err +func NewManager(cfg *config.Server, inK8s bool) (extTypes.Manager, error) { + var cli k8scli.Client + var err error + if inK8s { + cli, err = k8scli.New(k8sclicfg.GetConfigOrDie(), k8scli.Options{Scheme: envoygateway.GetScheme()}) + if err != nil { + return nil, err + } } var extension *egv1a1.ExtensionManager @@ -271,6 +275,9 @@ func setupGRPCOpts(ctx context.Context, client k8scli.Client, ext *egv1a1.Extens if ext.Service == nil { return nil, errors.New("the registered extension doesn't have a service config") } + if ext.Service.TLS != nil && client == nil { + return nil, errors.New("the registered extension's service config has TLS enabled but no k8s client was provided") + } var opts []grpc.DialOption if ext.Service.TLS != nil { From 9226069866446b8573096e933308c0f331da2c40 Mon Sep 17 00:00:00 2001 From: Melissa Salazar Date: Mon, 12 May 2025 17:47:28 -0700 Subject: [PATCH 58/66] docs: Add new conceptual pages for intro concepts (#5981) * add new conceptual pages for intro concepts Signed-off-by: melsal13 * renamed envoy-proxy.md to proxy.md Signed-off-by: melsal13 * reorganized sidebar Signed-off-by: melsal13 * fixed formatting issue Signed-off-by: melsal13 * fixed linker errors Signed-off-by: melsal13 * fixed link errors in v1.3 Signed-off-by: melsal13 * fixed typo & removed related resources links in gateway-api-extensions index file Signed-off-by: melsal13 --------- Signed-off-by: melsal13 Signed-off-by: Arko Dasgupta --- .../en/latest/concepts/introduction/_index.md | 40 +++++++++ .../concepts/introduction/api-gateways.md | 27 ++++++ .../concepts/introduction/gateway-api.md | 35 ++++++++ .../gateway_api_extensions/_index.md | 46 ++++++++++ .../backend-traffic-policy.md | 82 +++++++++++++++++ .../client-traffic-policy.md | 88 +++++++++++++++++++ .../gateway_api_extensions/security-policy.md | 81 +++++++++++++++++ .../en/latest/concepts/introduction/proxy.md | 33 +++++++ .../en/v1.3/concepts/introduction/_index.md | 40 +++++++++ .../concepts/introduction/api-gateways.md | 27 ++++++ .../v1.3/concepts/introduction/gateway-api.md | 35 ++++++++ .../gateway_api_extensions/_index.md | 46 ++++++++++ .../backend-traffic-policy.md | 82 +++++++++++++++++ .../client-traffic-policy.md | 88 +++++++++++++++++++ .../gateway_api_extensions/security-policy.md | 81 +++++++++++++++++ .../en/v1.3/concepts/introduction/proxy.md | 33 +++++++ 16 files changed, 864 insertions(+) create mode 100644 site/content/en/latest/concepts/introduction/_index.md create mode 100644 site/content/en/latest/concepts/introduction/api-gateways.md create mode 100644 site/content/en/latest/concepts/introduction/gateway-api.md create mode 100644 site/content/en/latest/concepts/introduction/gateway_api_extensions/_index.md create mode 100644 site/content/en/latest/concepts/introduction/gateway_api_extensions/backend-traffic-policy.md create mode 100644 site/content/en/latest/concepts/introduction/gateway_api_extensions/client-traffic-policy.md create mode 100644 site/content/en/latest/concepts/introduction/gateway_api_extensions/security-policy.md create mode 100644 site/content/en/latest/concepts/introduction/proxy.md create mode 100644 site/content/en/v1.3/concepts/introduction/_index.md create mode 100644 site/content/en/v1.3/concepts/introduction/api-gateways.md create mode 100644 site/content/en/v1.3/concepts/introduction/gateway-api.md create mode 100644 site/content/en/v1.3/concepts/introduction/gateway_api_extensions/_index.md create mode 100644 site/content/en/v1.3/concepts/introduction/gateway_api_extensions/backend-traffic-policy.md create mode 100644 site/content/en/v1.3/concepts/introduction/gateway_api_extensions/client-traffic-policy.md create mode 100644 site/content/en/v1.3/concepts/introduction/gateway_api_extensions/security-policy.md create mode 100644 site/content/en/v1.3/concepts/introduction/proxy.md diff --git a/site/content/en/latest/concepts/introduction/_index.md b/site/content/en/latest/concepts/introduction/_index.md new file mode 100644 index 0000000000..52e0098edf --- /dev/null +++ b/site/content/en/latest/concepts/introduction/_index.md @@ -0,0 +1,40 @@ +--- +title: "Introduction" +weight: 1 +--- + +## Overview + +**Envoy Gateway** is a Kubernetes-native [API Gateway](api-gateways.md) and reverse proxy control plane. It simplifies deploying and operating [Envoy Proxy](proxy.md) as a data plane by using the standard [Gateway API](gateway-api.md) and its own extensible APIs. + +By combining Envoy's performance and flexibility with Kubernetes-native configuration, Envoy Gateway helps platform teams expose and manage secure, observable, and scalable APIs with minimal operational overhead. + +## Why Use Envoy Gateway? + +Traditionally, configuring Envoy Proxy required deep networking expertise and writing complex configuration files. Envoy Gateway removes that barrier by: + +- Integrating tightly with Kubernetes through the Gateway API +- Providing custom CRDs for advanced traffic policies +- Automatically translating Kubernetes resources into Envoy config +- Managing the lifecycle of Envoy Proxy instances + +Envoy Gateway is designed to be **simple for app developers**, **powerful for platform engineers**, and **production-ready for large-scale deployments**. + +## Structure + +The different layers of Envoy Gateway are the following: + +| Layer | Description | +|----------------|-------------| +| **User Configuration** | Users define routing, security, and traffic policies using standard Kubernetes Gateway API resources, optionally extended with Envoy Gateway CRDs.| +| **Envoy Gateway Controller** | A control plane component that watches Gateway API and Envoy Gateway-specific resources, translates them, and produces configuration for Envoy Proxy.| +| **Envoy Proxy(Data Plane)** | A high-performance proxy that receives and handles live traffic according to the configuration generated by Envoy Gateway.| + +Together, these layers create a system that's: +- Easy to configure +- Powerful enough for complex needs +- Standardized and familiar +- Ready for the future + +## Next Steps +For a deeper understanding of Envoy Gateway’s building blocks, you may also wish to explore these conceptual guides: \ No newline at end of file diff --git a/site/content/en/latest/concepts/introduction/api-gateways.md b/site/content/en/latest/concepts/introduction/api-gateways.md new file mode 100644 index 0000000000..3583e11d2d --- /dev/null +++ b/site/content/en/latest/concepts/introduction/api-gateways.md @@ -0,0 +1,27 @@ +--- +title: "API Gateways" +weight: 3 +--- + +## Overview +An API gateway is a centralized entry point for managing, securing, and routing requests to backend services. It handles cross-cutting concerns, like authentication, rate limiting, and protocol translation, so individual services don’t have to. Decoupling clients from internal systems simplifies scaling, enforces consistency, and reduces redundancy. + +## Use Cases + +Use an API Gateway to: +- Avoid duplicating logic across microservices. +- Create a central point of control for access, monitoring, and traffic rules. +- Expose internal services to the public internet. +- Provide protocol support for HTTP, gRPC, or TLS. +- Enforce policies and see traffic metrics at the edge. + +## API Gateways in relation to Envoy Gateway + +Under the hood, Envoy Proxy is a powerful, production-grade proxy that supports many of the capabilities you'd expect from an API Gateway, like traffic routing, retries, TLS termination, observability, and more. However, configuring Envoy directly can be complex and verbose. + +Envoy Gateway makes configuring Envoy Proxy simple by implementing and extending the Kubernetes-native Gateway API. You define high-level traffic rules using resources like Gateway, HTTPRoute, or TLSRoute, and Envoy Gateway automatically translates them into detailed Envoy Proxy configurations. + +## Related Resources + +- [Getting Started with Envoy Gateway](../../tasks/quickstart.md) +- [Kubernetes Gateway API](https://gateway-api.sigs.k8s.io/) \ No newline at end of file diff --git a/site/content/en/latest/concepts/introduction/gateway-api.md b/site/content/en/latest/concepts/introduction/gateway-api.md new file mode 100644 index 0000000000..608893fffc --- /dev/null +++ b/site/content/en/latest/concepts/introduction/gateway-api.md @@ -0,0 +1,35 @@ +--- +title: "The Gateway API" +weight: 1 +--- + +## Before You Begin +You may want to be familiar with: +- [Kubernetes Gateway API](https://gateway-api.sigs.k8s.io/) +- [Kubernetes Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) + +## Overview +The Gateway API is a Kubernetes API designed to provide a consistent, expressive, and extensible method for managing network traffic into and within a Kubernetes cluster, compared to the legacy Ingress API. It introduces core resources such as `GatewayClass` and `Gateway` and various route types like `HTTPRoute` and `TLSRoute`, which allow you to define how traffic is routed, secured, and exposed. + +The Gateway API succeeds the Ingress API, which many Kubernetes users may already be familiar with. The Ingress API provided a mechanism for exposing HTTP(S) services to external traffic. The lack of advanced features like regex path matching led to custom annotations to compensate for these deficiencies. This non-standard approach led to fragmentation across Ingress Controllers, challenging portability. + +## Use Cases +Use The Gateway API to: +- Define how external traffic enters and is routed within your cluster +- Configure HTTP(S), TLS, and TCP traffic routing in a standardized, Kubernetes-native way +- Apply host-based, path-based, and header-based routing rules using HTTPRoute +- Terminate TLS at the edge using Gateway TLS configuration +- Separate responsibilities between infrastructure and application teams through role-oriented resource design +- Improve portability and consistency across different gateway implementations + +## The Gateway API in Envoy Gateway +In essence, the Gateway API provides a standard interface. Envoy Gateway adds production-grade capabilities to that interface, bridging the gap between simplicity and power while keeping everything Kubernetes-native. + +One of the Gateway API's key strengths is that implementers can extend it. While providing a foundation for standard routing and traffic control needs, it enables implementations to introduce custom resources that address specific use cases. + +Envoy Gateway leverages this model by introducing a suite of Gateway API extensions—implemented as Kubernetes Custom Resource Definitions (CRDs)—to expose powerful features from Envoy Proxy. These features include enhanced support for rate limiting, authentication, traffic shaping, and more. By utilizing these extensions, users can access production-grade functionality in a Kubernetes-native and declarative manner, without needing to write a low-level Envoy configuration. + +## Related Resources +- [Getting Started with Envoy Gateway](../../tasks/quickstart.md) +- [Envoy Gateway API Reference](../../api/extension_types) +- [Extensibility Tasks](../../tasks/extensibility/_index.md) diff --git a/site/content/en/latest/concepts/introduction/gateway_api_extensions/_index.md b/site/content/en/latest/concepts/introduction/gateway_api_extensions/_index.md new file mode 100644 index 0000000000..26d3789db1 --- /dev/null +++ b/site/content/en/latest/concepts/introduction/gateway_api_extensions/_index.md @@ -0,0 +1,46 @@ +--- +title: "Gateway API Extensions" +weight: 2 +--- +## Before You Begin +- [The Gateway API](https://gateway-api.sigs.k8s.io/) + +## Overview +Gateway API Extensions let you configure extra features that aren’t part of the standard Kubernetes Gateway API. These extensions are built by the teams that create and maintain Gateway API implementations. +The Gateway API was designed to be extensible safe, and reliable. In the old Ingress API, people had to use custom annotations to add new features, but those weren’t type-safe, making it hard to check if their configuration was correct. +With Gateway API Extensions, implementers provide type-safe Custom Resource Definitions (CRDs). This means every configuration you write has a clear structure and strict rules, making it easier to catch mistakes early and be confident your setup is valid. +## Use Cases + +Here are some examples of what kind of features extensions include: + +1. **Advanced Traffic Management:** + Implementing sophisticated load balancing algorithms, circuit breaking, or retries not defined in the core API +2. **Enhanced Security Controls:** + Adding implementation-specific TLS configurations, authentication mechanisms, or access control rules +3. **Observability Integration:** + Connecting Gateway resources to monitoring systems, logging pipelines, or tracing frameworks +4. **Custom Protocol Support:** + Extending beyond HTTP/TCP/UDP with specialized protocol handling +5. **Rate Limiting and Compression:** + Implementing traffic policing specific to the implementation's capabilities + +## Gateway API Extensions in Envoy Gateway + +The Envoy Gateway API introduces a set of Gateway API extensions that enable users to leverage the power of the Envoy proxy. Envoy Gateway uses a policy attachment model, where custom policies are applied to standard Gateway API resources (like HTTPRoute or Gateway) without modifying the core API. This approach provides separation of concerns and makes it easier to manage configurations across teams. + +{{% alert title="Current Extensions" color="info" %}} +Currently supported extensions include +[`Backend`](../../../api/extension_types#backend), +[`BackendTrafficPolicy`](../../../api/extension_types#backendtrafficpolicy), +[`ClientTrafficPolicy`](../../../api/extension_types#clienttrafficpolicy), +[`EnvoyExtensionPolicy`](../../../api/extension_types#envoyextensionpolicy), +[`EnvoyGateway`](../../../api/extension_types#envoygateway), +[`EnvoyPatchPolicy`](../../../api/extension_types#envoypatchpolicy), +[`EnvoyProxy`](../../../api/extension_types#envoyproxy), +[`HTTPRouteFilter`](../../../api/extension_types#httproutefilter), and +[`SecurityPolicy`](../../../api/extension_types#securitypolicy), +{{% /alert %}} + +These extensions are processed through Envoy Gateway's control plane, translating them into xDS configurations applied to Envoy Proxy instances. This layered architecture allows for consistent, scalable, and production-grade traffic control without needing to manage raw Envoy configuration directly. + +## Related Resources diff --git a/site/content/en/latest/concepts/introduction/gateway_api_extensions/backend-traffic-policy.md b/site/content/en/latest/concepts/introduction/gateway_api_extensions/backend-traffic-policy.md new file mode 100644 index 0000000000..b0313539e2 --- /dev/null +++ b/site/content/en/latest/concepts/introduction/gateway_api_extensions/backend-traffic-policy.md @@ -0,0 +1,82 @@ +--- +title: "BackendTrafficPolicy" +--- +## Before you Begin +- [Gateway API Extensions](_index.md) + +## Overview +`BackendTrafficPolicy` is an extension to the Kubernetes Gateway API that controls how Envoy Gateway communicates with your backend services. It can configure connection behavior, resilience mechanisms, and performance optimizations without requiring changes to your applications. + +Think of it as a traffic controller between your gateway and backend services. It can detect problems, prevent failures from spreading, and optimize request handling to improve system stability. + +## Use Cases + +`BackendTrafficPolicy` is particularly useful in scenarios where you need to: + +1. **Protect your services:** + Limit connections and reject excess traffic when necessary + +2. **Build resilient systems:** + Detect failing services and redirect traffic + +3. **Improve performance:** + Optimize how requests are distributed and responses are handled + +4. **Test system behavior:** + Inject faults and validate your recovery mechanisms + +## BackendTrafficPolicy in Envoy Gateway + +`BackendTrafficPolicy` is part of the Envoy Gateway API suite, which extends the Kubernetes Gateway API with additional capabilities. It's implemented as a Custom Resource Definition (CRD) that you can use to configure how Envoy Gateway manages traffic to your backend services. + +You can attach it to Gateway API resources in two ways: + +1. Using `targetRefs` to directly reference specific Gateway resources +2. Using `targetSelectors` to match Gateway resources based on labels + +The policy applies to all resources that match either targeting method. When multiple policies target the same resource, the most specific configuration wins. + +For example, consider these two policies: + +```yaml +# Policy 1: Applies to all routes in the gateway +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: gateway-policy +spec: + targetRefs: + - kind: Gateway + name: my-gateway + circuitBreaker: + maxConnections: 100 + +--- +# Policy 2: Applies to a specific route +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: route-policy +spec: + targetRefs: + - kind: HTTPRoute + name: my-route + circuitBreaker: + maxConnections: 50 +``` + +In this example `my-route` and `my-gateway` would both affect the route. However, since Policy 2 targets the route directly while Policy 1 targets the gateway, Policy 2's configuration (`maxConnections: 50`) will take precedence for that specific route. + +Lastly, it's important to note that even when you apply a policy to a Gateway, the policy's effects are tracked separately for each backend service referenced in your routes. For example, if you set up circuit breaking on a Gateway with multiple backend services, each backend service will have its own independent circuit breaker counter. This ensures that issues with one backend service don't affect the others. + +## Related Resources + +- [Circuit Breakers](../../../tasks/traffic/circuit-breaker.md) +- [Failover](../../../tasks/traffic/failover) +- [Fault Injection](../../../tasks/traffic/fault-injection) +- [Global Rate Limit](../../../tasks/traffic/global-rate-limit) +- [Local Rate Limit](../../../tasks/traffic/local-rate-limit) +- [Load Balancing](../../../tasks/traffic/load-balancing) +- [Response Compression](../../../tasks/traffic/response-compression) +- [Response Override](../../../tasks/traffic/response-override) +- [BackendTrafficPolicy API Reference](../../../api/extension_types#backendtrafficpolicy) diff --git a/site/content/en/latest/concepts/introduction/gateway_api_extensions/client-traffic-policy.md b/site/content/en/latest/concepts/introduction/gateway_api_extensions/client-traffic-policy.md new file mode 100644 index 0000000000..923ba9fbc9 --- /dev/null +++ b/site/content/en/latest/concepts/introduction/gateway_api_extensions/client-traffic-policy.md @@ -0,0 +1,88 @@ +--- +title: "ClientTrafficPolicy" +--- +## Before you Begin +- [Gateway API Extensions](_index.md) + +## Overview + +`ClientTrafficPolicy` is an extension to the Kubernetes Gateway API that allows system administrators to configure how the Envoy Proxy server behaves with downstream clients. It is a policy attachment resource that can be applied to Gateway resources and holds settings for configuring the behavior of the connection between the downstream client and Envoy Proxy listener. + +Think of `ClientTrafficPolicy` as a set of rules for your Gateway's entry points, it lets you configure specific behaviors for each listener in your Gateway, with more specific rules taking precedence over general ones. + +## Use Cases + +`ClientTrafficPolicy` is particularly useful in scenarios where you need to: + +1. **Enforce TLS Security** + Configure TLS termination, mutual TLS (mTLS), and certificate validation at the edge. + +2. **Manage Client Connections** + Control TCP keepalive behavior and connection timeouts for optimal resource usage. + +3. **Handle Client Identity** + Configure trusted proxy chains to correctly resolve client IPs for logging and access control. + +4. **Normalize Request Paths** + Sanitize incoming request paths to ensure compatibility with backend routing rules. + +5. **Tune HTTP Protocols** + Configure HTTP/1, HTTP/2, and HTTP/3 settings for compatibility and performance. + +6. **Monitor Listener Health** + Set up health checks for integration with load balancers and failover mechanisms. + +## ClientTrafficPolicy in Envoy Gateway + +`ClientTrafficPolicy` is part of the Envoy Gateway API suite, which extends the Kubernetes Gateway API with additional capabilities. It's implemented as a Custom Resource Definition (CRD) that you can use to configure how Envoy Gateway manages incoming client traffic. + +You can attach it to Gateway API resources in two ways: + +1. Using `targetRefs` to directly reference specific Gateway resources +2. Using `targetSelectors` to match Gateway resources based on labels + +The policy applies to all Gateway resources that match either targeting method. When multiple policies target the same resource, the most specific configuration wins. + +For example, consider these policies targeting the same Gateway Listener: + +```yaml +# Policy A: Targets a specific listener in the gateway +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: ClientTrafficPolicy +metadata: + name: listener-specific-policy +spec: + targetRefs: + - kind: Gateway + name: my-gateway + sectionName: https-listener # Targets specific listener + timeout: + http: + idleTimeout: 30s + +--- +# Policy B: Targets the entire gateway +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: ClientTrafficPolicy +metadata: + name: gateway-wide-policy +spec: + targetRefs: + - kind: Gateway + name: my-gateway # Targets all listeners + timeout: + http: + idleTimeout: 60s +``` + +In this case: +- Policy A will be applied/attached to the specific Listener defined in the `targetRef.SectionName` +- Policy B will be applied to the remaining Listeners within the Gateway. Policy B will have an additional status condition Overridden=True. + +## Related Resources + +- [Connection Limit](../../../tasks/traffic/connection-limit.md) +- [HTTP Request Headers](../../../tasks/traffic/http-request-headers) +- [HTTP/3](../../../tasks/traffic/http3) +- [Mutual TLS: External Clients to the Gateway](../../../tasks/security/mutual-tls.md) +- [ClientTrafficPolicy API Reference](../../../api/extension_types#clienttrafficpolicy) diff --git a/site/content/en/latest/concepts/introduction/gateway_api_extensions/security-policy.md b/site/content/en/latest/concepts/introduction/gateway_api_extensions/security-policy.md new file mode 100644 index 0000000000..cc0a273916 --- /dev/null +++ b/site/content/en/latest/concepts/introduction/gateway_api_extensions/security-policy.md @@ -0,0 +1,81 @@ +--- +title: "SecurityPolicy" +--- + +## Before you Begin +- [Gateway API Extensions](_index.md) + +## Overview + +`SecurityPolicy` is an Envoy Gateway extension to the Kubernetes Gateway API that allows you to define authentication and authorization requirements for traffic entering your gateway. It acts as a security layer that only properly authenticated and authorized requests are allowed through your backend services. + +`SecurityPolicy` is designed for you to enforce access controls through configuration at the edge of your infrastructure in a declarative, Kubernetes-native way, without needing to configure complex proxy rules manually. + +## Use Cases + +1. **Authentication Methods:** + - Authenticate client apps using mTLS, JWTs, API keys, or Basic Auth + - Authenticate users with OIDC Provider integration + +2. **Authorization Controls:** + - Define and enforce authorization rules based on user roles and permissions + - Integrate with external authorization services for real-time policy decisions + - JWT Token Authorization Checks + +3. **Cross-Origin Security:** + - Configure CORS to allow or restrict cross-origin requests for APIs + +## SecurityPolicy in Envoy Gateway + +`SecurityPolicy` is implemented as a Kubernetes Custom Resource Definition (CRD) and follows the policy attachment model. You can attach it to Gateway API resources in two ways: + +1. Using `targetRefs` to directly reference specific Gateway resources +2. Using `targetSelectors` to match Gateway resources based on labels + +The policy applies to all resources that match either targeting method. When multiple policies target the same resource, the most specific configuration wins. + +For example, consider these policies targeting the same Gateway Listener: + +```yaml +# Policy A: Applies to a specific listener +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: listener-policy + namespace: default +spec: + targetRefs: + - kind: Gateway + name: my-gateway + sectionName: https # Applies only to "https" listener + cors: + allowOrigins: + - exact: https://example.com +--- +# Policy B: Applies to the entire gateway +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: gateway-policy + namespace: default +spec: + targetRefs: + - kind: Gateway + name: my-gateway # Applies to all listeners + cors: + allowOrigins: + - exact: https://default.com +``` + +In the example, policy A affects only the HTTPS listener, while policy B applies to the rest of the listeners in the gateway. Since Policy A is more specific, the system will show Overridden=True for Policy B on the https listener. + +## Related Resources +- [API Key Authentication](../../../tasks/security/apikey-auth.md) +- [Basic Authentication](../../../tasks/security/basic-auth.md) +- [CORS](../../../tasks/security/cors.md) +- [External Authorization](../../../tasks/security/ext-auth.md) +- [IP Allowlist/Denylist](../../../tasks/security/restrict-ip-access.md) +- [JWT Authentication](../../../tasks/security/jwt-authentication.md) +- [JWT Claim Based Authorization](../../../tasks/security/jwt-claim-authorization.md) +- [OIDC Authorization](../../../tasks/security/oidc.md) +- [SecurityPolicy API Reference](../../../api/extension_types#securitypolicy) diff --git a/site/content/en/latest/concepts/introduction/proxy.md b/site/content/en/latest/concepts/introduction/proxy.md new file mode 100644 index 0000000000..fab550ae89 --- /dev/null +++ b/site/content/en/latest/concepts/introduction/proxy.md @@ -0,0 +1,33 @@ +--- +title: "Proxy" +weight: 4 +--- + +## Overview +**A proxy server is an intermediary between a client (like a web browser) and another server (like an API server).** When the client makes a request, the proxy forwards it to the destination server, receives the response, and then sends it back to the client. + +Proxies are used to enhance security, manage traffic, anonymize user activity, or optimize performance through caching and load balancing features. In cloud environments, they often handle critical tasks such as request routing, TLS termination, authentication, and traffic shaping. + +## Use Cases + +**Use Envoy Proxy to:** +- Manage internal or external traffic with a powerful L3/L4/L7 proxy +- Control HTTP, gRPC, or TLS routing with fine-grained match and rewrite rules +- Gain full observability via built-in metrics, tracing, and logging +- Implement intelligent load balancing and resilient failover strategies +- Integrate seamlessly with service meshes, API gateways, and other control planes + +## Proxy in Envoy Gateway +Envoy Gateway is a system made up of two main parts: +- A _data plane_, which handles the actual network traffic +- A _control plane_, which manages and configures the _data plane_ + +Envoy Gateway uses the Envoy Proxy, which was originally developed at Lyft. This proxy is the foundation of the Envoy project, of which Envoy Gateway is a part, and is now a graduated project within the Cloud Native Computing Foundation (CNCF). + +Envoy Proxy is a high-performance, open-source proxy designed for cloud-native applications. Envoy supports use cases for edge and service proxies, routing traffic at the system’s boundary or between internal services. + +The control plane uses the Kubernetes Gateway API to understand your settings and then translates them into the format Envoy Proxy needs (called _xDS configuration_). It also runs and updates the Envoy Proxy instances inside your Kubernetes cluster. + +## Related Resources +- [Getting Started with Envoy Gateway](../../tasks/quickstart.md) +- [Envoy Proxy](https://www.envoyproxy.io/) diff --git a/site/content/en/v1.3/concepts/introduction/_index.md b/site/content/en/v1.3/concepts/introduction/_index.md new file mode 100644 index 0000000000..52e0098edf --- /dev/null +++ b/site/content/en/v1.3/concepts/introduction/_index.md @@ -0,0 +1,40 @@ +--- +title: "Introduction" +weight: 1 +--- + +## Overview + +**Envoy Gateway** is a Kubernetes-native [API Gateway](api-gateways.md) and reverse proxy control plane. It simplifies deploying and operating [Envoy Proxy](proxy.md) as a data plane by using the standard [Gateway API](gateway-api.md) and its own extensible APIs. + +By combining Envoy's performance and flexibility with Kubernetes-native configuration, Envoy Gateway helps platform teams expose and manage secure, observable, and scalable APIs with minimal operational overhead. + +## Why Use Envoy Gateway? + +Traditionally, configuring Envoy Proxy required deep networking expertise and writing complex configuration files. Envoy Gateway removes that barrier by: + +- Integrating tightly with Kubernetes through the Gateway API +- Providing custom CRDs for advanced traffic policies +- Automatically translating Kubernetes resources into Envoy config +- Managing the lifecycle of Envoy Proxy instances + +Envoy Gateway is designed to be **simple for app developers**, **powerful for platform engineers**, and **production-ready for large-scale deployments**. + +## Structure + +The different layers of Envoy Gateway are the following: + +| Layer | Description | +|----------------|-------------| +| **User Configuration** | Users define routing, security, and traffic policies using standard Kubernetes Gateway API resources, optionally extended with Envoy Gateway CRDs.| +| **Envoy Gateway Controller** | A control plane component that watches Gateway API and Envoy Gateway-specific resources, translates them, and produces configuration for Envoy Proxy.| +| **Envoy Proxy(Data Plane)** | A high-performance proxy that receives and handles live traffic according to the configuration generated by Envoy Gateway.| + +Together, these layers create a system that's: +- Easy to configure +- Powerful enough for complex needs +- Standardized and familiar +- Ready for the future + +## Next Steps +For a deeper understanding of Envoy Gateway’s building blocks, you may also wish to explore these conceptual guides: \ No newline at end of file diff --git a/site/content/en/v1.3/concepts/introduction/api-gateways.md b/site/content/en/v1.3/concepts/introduction/api-gateways.md new file mode 100644 index 0000000000..3583e11d2d --- /dev/null +++ b/site/content/en/v1.3/concepts/introduction/api-gateways.md @@ -0,0 +1,27 @@ +--- +title: "API Gateways" +weight: 3 +--- + +## Overview +An API gateway is a centralized entry point for managing, securing, and routing requests to backend services. It handles cross-cutting concerns, like authentication, rate limiting, and protocol translation, so individual services don’t have to. Decoupling clients from internal systems simplifies scaling, enforces consistency, and reduces redundancy. + +## Use Cases + +Use an API Gateway to: +- Avoid duplicating logic across microservices. +- Create a central point of control for access, monitoring, and traffic rules. +- Expose internal services to the public internet. +- Provide protocol support for HTTP, gRPC, or TLS. +- Enforce policies and see traffic metrics at the edge. + +## API Gateways in relation to Envoy Gateway + +Under the hood, Envoy Proxy is a powerful, production-grade proxy that supports many of the capabilities you'd expect from an API Gateway, like traffic routing, retries, TLS termination, observability, and more. However, configuring Envoy directly can be complex and verbose. + +Envoy Gateway makes configuring Envoy Proxy simple by implementing and extending the Kubernetes-native Gateway API. You define high-level traffic rules using resources like Gateway, HTTPRoute, or TLSRoute, and Envoy Gateway automatically translates them into detailed Envoy Proxy configurations. + +## Related Resources + +- [Getting Started with Envoy Gateway](../../tasks/quickstart.md) +- [Kubernetes Gateway API](https://gateway-api.sigs.k8s.io/) \ No newline at end of file diff --git a/site/content/en/v1.3/concepts/introduction/gateway-api.md b/site/content/en/v1.3/concepts/introduction/gateway-api.md new file mode 100644 index 0000000000..608893fffc --- /dev/null +++ b/site/content/en/v1.3/concepts/introduction/gateway-api.md @@ -0,0 +1,35 @@ +--- +title: "The Gateway API" +weight: 1 +--- + +## Before You Begin +You may want to be familiar with: +- [Kubernetes Gateway API](https://gateway-api.sigs.k8s.io/) +- [Kubernetes Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) + +## Overview +The Gateway API is a Kubernetes API designed to provide a consistent, expressive, and extensible method for managing network traffic into and within a Kubernetes cluster, compared to the legacy Ingress API. It introduces core resources such as `GatewayClass` and `Gateway` and various route types like `HTTPRoute` and `TLSRoute`, which allow you to define how traffic is routed, secured, and exposed. + +The Gateway API succeeds the Ingress API, which many Kubernetes users may already be familiar with. The Ingress API provided a mechanism for exposing HTTP(S) services to external traffic. The lack of advanced features like regex path matching led to custom annotations to compensate for these deficiencies. This non-standard approach led to fragmentation across Ingress Controllers, challenging portability. + +## Use Cases +Use The Gateway API to: +- Define how external traffic enters and is routed within your cluster +- Configure HTTP(S), TLS, and TCP traffic routing in a standardized, Kubernetes-native way +- Apply host-based, path-based, and header-based routing rules using HTTPRoute +- Terminate TLS at the edge using Gateway TLS configuration +- Separate responsibilities between infrastructure and application teams through role-oriented resource design +- Improve portability and consistency across different gateway implementations + +## The Gateway API in Envoy Gateway +In essence, the Gateway API provides a standard interface. Envoy Gateway adds production-grade capabilities to that interface, bridging the gap between simplicity and power while keeping everything Kubernetes-native. + +One of the Gateway API's key strengths is that implementers can extend it. While providing a foundation for standard routing and traffic control needs, it enables implementations to introduce custom resources that address specific use cases. + +Envoy Gateway leverages this model by introducing a suite of Gateway API extensions—implemented as Kubernetes Custom Resource Definitions (CRDs)—to expose powerful features from Envoy Proxy. These features include enhanced support for rate limiting, authentication, traffic shaping, and more. By utilizing these extensions, users can access production-grade functionality in a Kubernetes-native and declarative manner, without needing to write a low-level Envoy configuration. + +## Related Resources +- [Getting Started with Envoy Gateway](../../tasks/quickstart.md) +- [Envoy Gateway API Reference](../../api/extension_types) +- [Extensibility Tasks](../../tasks/extensibility/_index.md) diff --git a/site/content/en/v1.3/concepts/introduction/gateway_api_extensions/_index.md b/site/content/en/v1.3/concepts/introduction/gateway_api_extensions/_index.md new file mode 100644 index 0000000000..26d3789db1 --- /dev/null +++ b/site/content/en/v1.3/concepts/introduction/gateway_api_extensions/_index.md @@ -0,0 +1,46 @@ +--- +title: "Gateway API Extensions" +weight: 2 +--- +## Before You Begin +- [The Gateway API](https://gateway-api.sigs.k8s.io/) + +## Overview +Gateway API Extensions let you configure extra features that aren’t part of the standard Kubernetes Gateway API. These extensions are built by the teams that create and maintain Gateway API implementations. +The Gateway API was designed to be extensible safe, and reliable. In the old Ingress API, people had to use custom annotations to add new features, but those weren’t type-safe, making it hard to check if their configuration was correct. +With Gateway API Extensions, implementers provide type-safe Custom Resource Definitions (CRDs). This means every configuration you write has a clear structure and strict rules, making it easier to catch mistakes early and be confident your setup is valid. +## Use Cases + +Here are some examples of what kind of features extensions include: + +1. **Advanced Traffic Management:** + Implementing sophisticated load balancing algorithms, circuit breaking, or retries not defined in the core API +2. **Enhanced Security Controls:** + Adding implementation-specific TLS configurations, authentication mechanisms, or access control rules +3. **Observability Integration:** + Connecting Gateway resources to monitoring systems, logging pipelines, or tracing frameworks +4. **Custom Protocol Support:** + Extending beyond HTTP/TCP/UDP with specialized protocol handling +5. **Rate Limiting and Compression:** + Implementing traffic policing specific to the implementation's capabilities + +## Gateway API Extensions in Envoy Gateway + +The Envoy Gateway API introduces a set of Gateway API extensions that enable users to leverage the power of the Envoy proxy. Envoy Gateway uses a policy attachment model, where custom policies are applied to standard Gateway API resources (like HTTPRoute or Gateway) without modifying the core API. This approach provides separation of concerns and makes it easier to manage configurations across teams. + +{{% alert title="Current Extensions" color="info" %}} +Currently supported extensions include +[`Backend`](../../../api/extension_types#backend), +[`BackendTrafficPolicy`](../../../api/extension_types#backendtrafficpolicy), +[`ClientTrafficPolicy`](../../../api/extension_types#clienttrafficpolicy), +[`EnvoyExtensionPolicy`](../../../api/extension_types#envoyextensionpolicy), +[`EnvoyGateway`](../../../api/extension_types#envoygateway), +[`EnvoyPatchPolicy`](../../../api/extension_types#envoypatchpolicy), +[`EnvoyProxy`](../../../api/extension_types#envoyproxy), +[`HTTPRouteFilter`](../../../api/extension_types#httproutefilter), and +[`SecurityPolicy`](../../../api/extension_types#securitypolicy), +{{% /alert %}} + +These extensions are processed through Envoy Gateway's control plane, translating them into xDS configurations applied to Envoy Proxy instances. This layered architecture allows for consistent, scalable, and production-grade traffic control without needing to manage raw Envoy configuration directly. + +## Related Resources diff --git a/site/content/en/v1.3/concepts/introduction/gateway_api_extensions/backend-traffic-policy.md b/site/content/en/v1.3/concepts/introduction/gateway_api_extensions/backend-traffic-policy.md new file mode 100644 index 0000000000..b0313539e2 --- /dev/null +++ b/site/content/en/v1.3/concepts/introduction/gateway_api_extensions/backend-traffic-policy.md @@ -0,0 +1,82 @@ +--- +title: "BackendTrafficPolicy" +--- +## Before you Begin +- [Gateway API Extensions](_index.md) + +## Overview +`BackendTrafficPolicy` is an extension to the Kubernetes Gateway API that controls how Envoy Gateway communicates with your backend services. It can configure connection behavior, resilience mechanisms, and performance optimizations without requiring changes to your applications. + +Think of it as a traffic controller between your gateway and backend services. It can detect problems, prevent failures from spreading, and optimize request handling to improve system stability. + +## Use Cases + +`BackendTrafficPolicy` is particularly useful in scenarios where you need to: + +1. **Protect your services:** + Limit connections and reject excess traffic when necessary + +2. **Build resilient systems:** + Detect failing services and redirect traffic + +3. **Improve performance:** + Optimize how requests are distributed and responses are handled + +4. **Test system behavior:** + Inject faults and validate your recovery mechanisms + +## BackendTrafficPolicy in Envoy Gateway + +`BackendTrafficPolicy` is part of the Envoy Gateway API suite, which extends the Kubernetes Gateway API with additional capabilities. It's implemented as a Custom Resource Definition (CRD) that you can use to configure how Envoy Gateway manages traffic to your backend services. + +You can attach it to Gateway API resources in two ways: + +1. Using `targetRefs` to directly reference specific Gateway resources +2. Using `targetSelectors` to match Gateway resources based on labels + +The policy applies to all resources that match either targeting method. When multiple policies target the same resource, the most specific configuration wins. + +For example, consider these two policies: + +```yaml +# Policy 1: Applies to all routes in the gateway +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: gateway-policy +spec: + targetRefs: + - kind: Gateway + name: my-gateway + circuitBreaker: + maxConnections: 100 + +--- +# Policy 2: Applies to a specific route +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: route-policy +spec: + targetRefs: + - kind: HTTPRoute + name: my-route + circuitBreaker: + maxConnections: 50 +``` + +In this example `my-route` and `my-gateway` would both affect the route. However, since Policy 2 targets the route directly while Policy 1 targets the gateway, Policy 2's configuration (`maxConnections: 50`) will take precedence for that specific route. + +Lastly, it's important to note that even when you apply a policy to a Gateway, the policy's effects are tracked separately for each backend service referenced in your routes. For example, if you set up circuit breaking on a Gateway with multiple backend services, each backend service will have its own independent circuit breaker counter. This ensures that issues with one backend service don't affect the others. + +## Related Resources + +- [Circuit Breakers](../../../tasks/traffic/circuit-breaker.md) +- [Failover](../../../tasks/traffic/failover) +- [Fault Injection](../../../tasks/traffic/fault-injection) +- [Global Rate Limit](../../../tasks/traffic/global-rate-limit) +- [Local Rate Limit](../../../tasks/traffic/local-rate-limit) +- [Load Balancing](../../../tasks/traffic/load-balancing) +- [Response Compression](../../../tasks/traffic/response-compression) +- [Response Override](../../../tasks/traffic/response-override) +- [BackendTrafficPolicy API Reference](../../../api/extension_types#backendtrafficpolicy) diff --git a/site/content/en/v1.3/concepts/introduction/gateway_api_extensions/client-traffic-policy.md b/site/content/en/v1.3/concepts/introduction/gateway_api_extensions/client-traffic-policy.md new file mode 100644 index 0000000000..923ba9fbc9 --- /dev/null +++ b/site/content/en/v1.3/concepts/introduction/gateway_api_extensions/client-traffic-policy.md @@ -0,0 +1,88 @@ +--- +title: "ClientTrafficPolicy" +--- +## Before you Begin +- [Gateway API Extensions](_index.md) + +## Overview + +`ClientTrafficPolicy` is an extension to the Kubernetes Gateway API that allows system administrators to configure how the Envoy Proxy server behaves with downstream clients. It is a policy attachment resource that can be applied to Gateway resources and holds settings for configuring the behavior of the connection between the downstream client and Envoy Proxy listener. + +Think of `ClientTrafficPolicy` as a set of rules for your Gateway's entry points, it lets you configure specific behaviors for each listener in your Gateway, with more specific rules taking precedence over general ones. + +## Use Cases + +`ClientTrafficPolicy` is particularly useful in scenarios where you need to: + +1. **Enforce TLS Security** + Configure TLS termination, mutual TLS (mTLS), and certificate validation at the edge. + +2. **Manage Client Connections** + Control TCP keepalive behavior and connection timeouts for optimal resource usage. + +3. **Handle Client Identity** + Configure trusted proxy chains to correctly resolve client IPs for logging and access control. + +4. **Normalize Request Paths** + Sanitize incoming request paths to ensure compatibility with backend routing rules. + +5. **Tune HTTP Protocols** + Configure HTTP/1, HTTP/2, and HTTP/3 settings for compatibility and performance. + +6. **Monitor Listener Health** + Set up health checks for integration with load balancers and failover mechanisms. + +## ClientTrafficPolicy in Envoy Gateway + +`ClientTrafficPolicy` is part of the Envoy Gateway API suite, which extends the Kubernetes Gateway API with additional capabilities. It's implemented as a Custom Resource Definition (CRD) that you can use to configure how Envoy Gateway manages incoming client traffic. + +You can attach it to Gateway API resources in two ways: + +1. Using `targetRefs` to directly reference specific Gateway resources +2. Using `targetSelectors` to match Gateway resources based on labels + +The policy applies to all Gateway resources that match either targeting method. When multiple policies target the same resource, the most specific configuration wins. + +For example, consider these policies targeting the same Gateway Listener: + +```yaml +# Policy A: Targets a specific listener in the gateway +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: ClientTrafficPolicy +metadata: + name: listener-specific-policy +spec: + targetRefs: + - kind: Gateway + name: my-gateway + sectionName: https-listener # Targets specific listener + timeout: + http: + idleTimeout: 30s + +--- +# Policy B: Targets the entire gateway +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: ClientTrafficPolicy +metadata: + name: gateway-wide-policy +spec: + targetRefs: + - kind: Gateway + name: my-gateway # Targets all listeners + timeout: + http: + idleTimeout: 60s +``` + +In this case: +- Policy A will be applied/attached to the specific Listener defined in the `targetRef.SectionName` +- Policy B will be applied to the remaining Listeners within the Gateway. Policy B will have an additional status condition Overridden=True. + +## Related Resources + +- [Connection Limit](../../../tasks/traffic/connection-limit.md) +- [HTTP Request Headers](../../../tasks/traffic/http-request-headers) +- [HTTP/3](../../../tasks/traffic/http3) +- [Mutual TLS: External Clients to the Gateway](../../../tasks/security/mutual-tls.md) +- [ClientTrafficPolicy API Reference](../../../api/extension_types#clienttrafficpolicy) diff --git a/site/content/en/v1.3/concepts/introduction/gateway_api_extensions/security-policy.md b/site/content/en/v1.3/concepts/introduction/gateway_api_extensions/security-policy.md new file mode 100644 index 0000000000..cc0a273916 --- /dev/null +++ b/site/content/en/v1.3/concepts/introduction/gateway_api_extensions/security-policy.md @@ -0,0 +1,81 @@ +--- +title: "SecurityPolicy" +--- + +## Before you Begin +- [Gateway API Extensions](_index.md) + +## Overview + +`SecurityPolicy` is an Envoy Gateway extension to the Kubernetes Gateway API that allows you to define authentication and authorization requirements for traffic entering your gateway. It acts as a security layer that only properly authenticated and authorized requests are allowed through your backend services. + +`SecurityPolicy` is designed for you to enforce access controls through configuration at the edge of your infrastructure in a declarative, Kubernetes-native way, without needing to configure complex proxy rules manually. + +## Use Cases + +1. **Authentication Methods:** + - Authenticate client apps using mTLS, JWTs, API keys, or Basic Auth + - Authenticate users with OIDC Provider integration + +2. **Authorization Controls:** + - Define and enforce authorization rules based on user roles and permissions + - Integrate with external authorization services for real-time policy decisions + - JWT Token Authorization Checks + +3. **Cross-Origin Security:** + - Configure CORS to allow or restrict cross-origin requests for APIs + +## SecurityPolicy in Envoy Gateway + +`SecurityPolicy` is implemented as a Kubernetes Custom Resource Definition (CRD) and follows the policy attachment model. You can attach it to Gateway API resources in two ways: + +1. Using `targetRefs` to directly reference specific Gateway resources +2. Using `targetSelectors` to match Gateway resources based on labels + +The policy applies to all resources that match either targeting method. When multiple policies target the same resource, the most specific configuration wins. + +For example, consider these policies targeting the same Gateway Listener: + +```yaml +# Policy A: Applies to a specific listener +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: listener-policy + namespace: default +spec: + targetRefs: + - kind: Gateway + name: my-gateway + sectionName: https # Applies only to "https" listener + cors: + allowOrigins: + - exact: https://example.com +--- +# Policy B: Applies to the entire gateway +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: gateway-policy + namespace: default +spec: + targetRefs: + - kind: Gateway + name: my-gateway # Applies to all listeners + cors: + allowOrigins: + - exact: https://default.com +``` + +In the example, policy A affects only the HTTPS listener, while policy B applies to the rest of the listeners in the gateway. Since Policy A is more specific, the system will show Overridden=True for Policy B on the https listener. + +## Related Resources +- [API Key Authentication](../../../tasks/security/apikey-auth.md) +- [Basic Authentication](../../../tasks/security/basic-auth.md) +- [CORS](../../../tasks/security/cors.md) +- [External Authorization](../../../tasks/security/ext-auth.md) +- [IP Allowlist/Denylist](../../../tasks/security/restrict-ip-access.md) +- [JWT Authentication](../../../tasks/security/jwt-authentication.md) +- [JWT Claim Based Authorization](../../../tasks/security/jwt-claim-authorization.md) +- [OIDC Authorization](../../../tasks/security/oidc.md) +- [SecurityPolicy API Reference](../../../api/extension_types#securitypolicy) diff --git a/site/content/en/v1.3/concepts/introduction/proxy.md b/site/content/en/v1.3/concepts/introduction/proxy.md new file mode 100644 index 0000000000..fab550ae89 --- /dev/null +++ b/site/content/en/v1.3/concepts/introduction/proxy.md @@ -0,0 +1,33 @@ +--- +title: "Proxy" +weight: 4 +--- + +## Overview +**A proxy server is an intermediary between a client (like a web browser) and another server (like an API server).** When the client makes a request, the proxy forwards it to the destination server, receives the response, and then sends it back to the client. + +Proxies are used to enhance security, manage traffic, anonymize user activity, or optimize performance through caching and load balancing features. In cloud environments, they often handle critical tasks such as request routing, TLS termination, authentication, and traffic shaping. + +## Use Cases + +**Use Envoy Proxy to:** +- Manage internal or external traffic with a powerful L3/L4/L7 proxy +- Control HTTP, gRPC, or TLS routing with fine-grained match and rewrite rules +- Gain full observability via built-in metrics, tracing, and logging +- Implement intelligent load balancing and resilient failover strategies +- Integrate seamlessly with service meshes, API gateways, and other control planes + +## Proxy in Envoy Gateway +Envoy Gateway is a system made up of two main parts: +- A _data plane_, which handles the actual network traffic +- A _control plane_, which manages and configures the _data plane_ + +Envoy Gateway uses the Envoy Proxy, which was originally developed at Lyft. This proxy is the foundation of the Envoy project, of which Envoy Gateway is a part, and is now a graduated project within the Cloud Native Computing Foundation (CNCF). + +Envoy Proxy is a high-performance, open-source proxy designed for cloud-native applications. Envoy supports use cases for edge and service proxies, routing traffic at the system’s boundary or between internal services. + +The control plane uses the Kubernetes Gateway API to understand your settings and then translates them into the format Envoy Proxy needs (called _xDS configuration_). It also runs and updates the Envoy Proxy instances inside your Kubernetes cluster. + +## Related Resources +- [Getting Started with Envoy Gateway](../../tasks/quickstart.md) +- [Envoy Proxy](https://www.envoyproxy.io/) From 03c6759ae8ec79c38c4f86b396ccb0ce91a3c059 Mon Sep 17 00:00:00 2001 From: Erica Hughberg Date: Mon, 12 May 2025 20:47:51 -0400 Subject: [PATCH 59/66] docs: update open graph image (#6022) update the og:image to a new image in style with the current website styling Signed-off-by: Erica Hughberg Signed-off-by: Arko Dasgupta --- site/content/en/featured-background.jpg | Bin 400690 -> 0 bytes site/hugo.toml | 4 ++-- site/static/img/envoy-gateway-feature.png | Bin 0 -> 606082 bytes 3 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 site/content/en/featured-background.jpg create mode 100644 site/static/img/envoy-gateway-feature.png diff --git a/site/content/en/featured-background.jpg b/site/content/en/featured-background.jpg deleted file mode 100644 index b1f8c56bc2993ff7b68a848d0fc6024fae6c8ade..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 400690 zcma%iWmFtNv*<4FPH=a(MT5JuxVuAecXtgCAXp%{h2Sn>ad(#hf#B}?`1HOX_q_A^ zoZac^>XPp1ny#*@d0Tqh0^mz|+gbqta&n9S1i=54w_^aNq=&h^4*&>&d2hA=0N%Es z6Kvewodwv~9Nkz=Eu73OS#7zd}rutVI`m@DgAGD?{`9!{~ni@mlvxS7ps%2 zH5&&%KR+8gCmSax%X<$NHy=lLQ*RbWH>&?okhF9&ceQnPw{>zP{|7}=GbaysAK&%)#LwL;GiMH+MD5{}JPV?d_)F<7~;MX6feS;c9OA-ks_{$nVql z|7P@$j_=Y4sJPm`KNr(al1}Cx4wjDYa*{%n?+{iCTMGdYC$||NKaV+!xh067#me;k zkKc^fl!e`#$CBHW(~_N&%k)2V{x^IM4h|5AUyM(ZgHxJ=LrOx7Q;JKHi-TK=hl59w zLz3%1xN?qe?xv3BmjB^x`_B8{xV-;QTmcDJOH+3zR}CkpPybm6U>hfQCpQ}>XL3$1 zRt|D{ZCghRCoeaKe>|gq4O!CC)z;I}LfX~If&8D7EMWV;2*7X3ZuTxcFAEPJHy4W~ z2Z)2kl!M)r#e$uipNE&VFFKf6Kqop@6`5==<-#fdAjhTMqye z0a_Cl01dXMqp#c8` zBOqb`fY4AdP;l_j2=MO+KoTG(6f^*af&&`^D2g%TV7JmhmhEho+|Dun4RaWyV# z5blMUYiOO@2MKi=#FX#m+<3iVlC(UY+w~e24N}r{yfdkV?wXb!#f|uo@U$W=s|flp zGP3W&0^enVgM){KgMonoq5^RagCl(_Fh;_;rWr9NRg`lMF-C&9W*-@0#sY)_AA@@)guiMaW}ZXXhQV+FjZpTp zXd~ggXV)IQxOEp`?2YO6hR#ZEyM>V zaEF7dKv(g=2~*lCh>4;485o!)GesJ_(8x!pP5R9e$%#1@73Bc3^MWH?p=-stj=n)H zITjRAx+(fSU{7G*)GE`Rqq8-~uKDZKZp&K6`dv!!2F{m{b`;gN{J!_@F8PlNlqsgY z>_S3@n);JaV>XePXPUzNXUG8rXvx6q7@3*{XP*b)_f2%*W}LlFK!(jaB;tZU}1sz75@n^J+`CLkna zz}VCrQvEwXZu$jO4Wf!$jw!sonUMu+M8r|aJc`iZox;??tT{_*-gsQkR7IkqLNmq( z_b6|SNDB;2s*H-#PVFx4``W+~k-h;&<#H<upO%j|4okGy9Z{?rVQoQSNpQ+ozsRTLpdy|XsZTZhf>+636tCgA9Ac{I` z)RD8}%|ag)6$&X?c#O^>ybEHWK zz~trcEJq&{jMQ zyn>WQgZUFyo+1S%Gy?uRPp-tH9rU~6?H(NOfD_q*1OUOv+U)XinS^NvP z084*u0?`N&WkC9hRlq_JF-bZSR>a52J_Rq1QoG;#T`C5dDxNV|0|(|pNEW>}8q(R> zN+WF@f*;JTqPn9PM};M2YG&cw)7Y2S5rR*d$FDxX4HCy?LG5h#)8YJ7IKt&aj0l}y zE}=)^P+w>+|MzDbXp#k{wxJH|GPbzLB#dfGteSBEUT>4t!GfXtaugD?w(!GNEXH(O z0Gmp62|_-Z(NM=I)L&=Oa6!C))%W0|0u_E`6&EG1WxGa zix10>)<=glNA7> zEbvfY=;dKPB+HKTg@}S_g+Hxm(~Q<6;v&pTm^FVHBEbH0zyb@E#|mC}vYD=%Z9Rawj;W=RbQ-o2AsmKy?Nlw( zpEExqQM@S>j^P{nK8`f+kx*u!RGFwtQb*kL(%_3{wN$2-<;7N>Su2C;K;zW$bR--I zjU?)kei|-%LiMq5i4nteE^nFo(wRbW4_Ch8(%X&js&oR!>6e}&>BhBh#FN3Xu z?ACJN!1IW!P-S~V_|@5V>to6~&SpL3>>Hpb*Ie2rd(2_0*1s;U&Di{heXYHIKIkrv zprNT{zENv@L|C^2s*^it*OJ&t9^=R(Fha{cX!>+z(AaY#*D-9=X7*2woum8A zoDg!X(@Z-+?(fFYEM>}~iH+aKk#=4*(UULDCtqtmP}p!eR+}8QhstsEALsG%?KO(< zx|mu@ue{Eff&w)&s&Q_ce;j3}Ts%f@FK`=rK8h>+xXFI1 zmjAsIjb)_3l{w`N164nwmc57VVi0Qy4Ud^DOsp}r4g!_H0} z8Q}tOPYGMG1LQiWF!}dyy22g5q_%QT8ICwsU5`fE9=n!q9BQ+K_OkQ69ES8(HlRt*_gt#T_Ch_|;O4}UAPu}j-)GW4x$FdSNuujq=m&kfu?go&B;s!^)2-FSaqa2kqQFI6iuN?wLLbzsY-fbM0GH4BP z$6%C`GHk|oYX>c&qN~PQyQz?n!HO9L8s>Jd#e7~Nn}imh&`m{Uzc6yDC=43jf!H^) zKQgky6Q!;|qplBBVgkVy@ibaNCzdnp>A5}_gemw;i9B0+IYn&N{z_>y{fa4CVz{;H zl+>LQwA?(uQL*RVYQ$@K7x1UE3i?vb{O6hKA3~x;S$f~43$c#yb4baTzcH>F_#Re8 z7jJ+xk-kI2C@!ONS0QmCr&X?}%`K7i*s(#Ut|$rBYz&ItO6Da8AT0#a~C!D>n{t#R`y?lkmJ1{+y8YjB|kGXjI%S2J>snEk@_{=&l1 zp%umWHValm)F{*HC2@Di%wc+ftp{5z38?Q9K7S7`iaeHtA%S@@@>5FJU^1GRycMll z1lkDd8BTS;h;%K|&`1cpypBnJZ#}Ho9|2t5p9IOTgW+_5MO@QCf5b`R*sGA~T44~rYtkCUZQ9O~;YQ|K3RR#(5FyUzV z%`=Rf1!TUOkk9y*VX(8)sTcw(j!}WmBVj`(6VlgyNm=SAWM#FRUq~>xGG8UdRFpBaw%6JtohpH;W zM{w$7^nc)4pY8B@m)S=!pV0~oJbZ5aj#3#3J@db!dDY|+CX^)9dyc=hmQ5sDHb&I%`swKvUt&PE2Bx!PY@oUH z*FGJb+-I!Bt0?i=@Ox%o3HG`)+ZT1d0Wd@^H?D$)m2k5XZE%Bz&MO(#j|uI%Sy zFas6OKAA>lH*PAMXb|!?fVxTxRJ>nDHLyIH}1gJHRwi0p;+f_RcPZc{= zuMmsD!jZd^OCITsEW97ODW(6$r6o&CCc^q64kAg%5-x$(*!Nt8He=ug)XFKEq^)Pz zC#S$R)fEVTVl>6E_^!7zt;J%eawHW}(>l^Gf8&IO{uWOw5CwB*Ngek~Dx2d%7YjxU z_yoEmKQ{8|6X>NbF_AcVn5!=_ISWbVLCP8IK z^(@kTZDJ{V2a@!eF)7*t$Jsjv_b6t+>f0P8qymzm<30(N>$*zOPl|&0xWg~sqcEq^ z8ZpPaLeS}tNWzms@TZudx9STwf>`7!Re>DPpOvd#SJGes_a<~4QsltT)Fsf+W4pg# zk+34ZHg#lQv3aZ{NGL2n=5{6LOs)}L6|Cy}(hrVv-E{gYiKuYY$Jpn<6Lt~WFXEcx zK{|hTGVj|yXmzWTe)KG=4!rKde*^exNQS)uP@kJtl?@;M!lk;o5j>rA?|NqN+|Ka_ zBV*q@HSa$etwVrg!=*Nwun+3qJf>HUjHj{NH^1ZF02KM}R+er>T*m2&5!LI}R_Jxt zl=0CfQ`6M$vM^ZBp4VlIw0p0s)}{57IB2up-WGNG_75A~wIv?2BEL4FFiW(5rH9|2 zqeBhJ=QGxKT5`qe8D~bR*{^@R%@4}S7N3u=L|j(d<=fVqSA=81yiN|@)wds(8U+TV&ZBkzS&m>tV%MjzkD&3P94Ua1|eS-iHzHk#D$}07uBkE#f=mU z{$d&2Xt$&kwfgm?_AR(&&o~wUTA(ts7b4;yxW1Mu9eUadO{V!l3KT+1~4`b+w``Ke3)LFs2x_Kpg?;bL{^6|x6! zTA>x*Q(CutQ}!36UzYerS>L|K3oEtA6hEshMwNX#Os@(OE2+1Az0QaeQ9L*NEX4gx z>4ZAjDOk|DMl@*Hw!o&}?lEe;dC1lrN@TC#eJq@}K)-F-_$8sZ#jVvmA>JGhnBe&k zFuXo{vF72gc=xJ1Z^!LlyE(5k63G0;kX=4aAIlKt$orMTKVxY?r*V=UB(pKlv0@d) zb@!o&6j5j0p|I&#ikt$$FH?rFX|G1fzMI={6z`Ddu=OwF^p@{6@tw?eVD#MX`1<|J z>Ovqh5uTY{*6bjAlIq&s&;8q6;vJRQi9+vlH2MQ`Y8TroUjBv;AHHuYK<0ebwR4%J zYp0jf@*cWy_ca3>TsKahO??j9{B5eeXqu+#l9N{ri%n6($4oVB=N-pyD~LDHK8<-2 zED+@kYP!+&wh4Dk3YA}p@XgIjZ#SY&Y237R>rN2T!gWC7eDq|3gZW(1uX92I70^|aQI?`#_eF#hWvxp~a1pGncgrN+9a<6M z1c&1RLYe^U(rXQhT+$pnn33>zlkq-CN}M1<9GQh^e2lZtS}Y0ps|qMAoas`&pf3%9 zMjUdtJ{TyrO}1$Y`vX7|nVnY1MkDXci&;vO206eBCzVcb7* zUPBml-Q!MxTR-?QHD?3q*7H~*<0@ZsGCSV_w&uyv+Az}%* z(E_lY;;VrAH^AOn*TR$!-ofIwXMn^nCrx#lB)L{e`hiwIzNt#xkm|h;Frzr*95=U9 zN0@^yWb>hNc(=sguFI`D=|1XiB!L%TDWxStBCpYn|CWoTpa4*=ZnhRW%_?meSnk7^ zLj9zGzkog8>aXTdw9XfrFV#4%hB!D5gwHgQQljx~Au!h`dSP%NUkh8BNGhveYA7gH zLISTK0t_3R26JYmVnVpce)4@>;CO+?4mh$$2!1gFj?*+Gk+0z7H+iUrHUR6`GFJs&}@%yPjLYQSX}UP)d>@1U7nQC5eQs;gy-`r zIM@yY-_$^Tb{QNJOfDvttcK6AE&pX_pu)0)sX-H)eVcFVHm`%hhWmr8-ar{ld?&}2 z6nSxQ9nUskxVSn{JKtyav9h-{&-6l2Ht4+kNHFNo zdas+yRNz%T40Rr|=4*KW+iE0eD|H;Zvp?o1k?-z`OUJptSe1$US&{pQ-9Of^@P84-SW|OYgdL030)?5W*OR z6YO)5Xt<%mc($X@zh~Fo zrhl}O#6tLr4^6+MCnsdWpyEL2h>oNS1>YsF!J%qJO`kynEfIHQ^TF5AUe##E&PBA^fpj0-b%iKk)`FHa1SfZT%z!weCnY#EGrTBnX{3<) zr&-jbWAqQ-Yi@)Sw^n7)ctOs0D);+DogNYzBkd}+3rFlYXNRwDd&GUoWQZymFJ%N8 ziKbzdtolut>uaWC{<=`RkB&w^pc(}yik`;E0hx^=M}9N;CCRV(x3S^*^-431O0E}K zfpNu$0-eu4?5S#JJ~?_Ym3HppPs8T84v$?0BPQSvsJX{FT$>5Cd$B2m&RH^?$~7Od z3_V9|cw%y1`5T=$F;r7G4+a=)eaqx_V+I>LB5))#|l+TS9M1ca3ZWc zti6}}z>#s~Ng2oeb=xGL)o-3}0MM6I-2N#EXqCeH$b4r4Yihjs^@9vj{DL2VynmasPoSFw!J{^#`gtUR7@!ss{CMflc|9QkO_t+ zcB|`7^)SD1y4%@dqd>XKp`-Gj?wIBiD*vz>u^Z^Gx8JiQgis@sF`#lIqeb(;5ksYL z6%0(lPFPyO?92&`f6l(Pw_*Afe5$s5G=b^XpZo0?Q6Ro>m-E1#>u^hGud`t0W5KEz zVTj88l-Y|O<@d|i2<}CMagXrJpw-y3$VL!KP$wg$+`A(zo6Lzch+Xi{N|QEbise*C zM?>2cy6$P>wMth#50TY|<+kI-l$(4I)Eq~rE5m70-)oI-;R~;fIf-M%%DYvreB8;H ztn#Edd1IZj>KxJ}SUu01`zMICb}xM^i6em^oVZHM=9yxdZwLiqmx;r6^TtTGfwqtXKb}n4m3`s)3o%+wY-%}G&)YDwA^Hb zW`vSzb1qhH(44V%LFXNWn}4IdQe=$cB}H@GCwG-+N`LQJRJHQ5stPu~obFoCz*C#_ z`*>PIop*X@&79$tI_%~~)gqv~=bqAWo2t8R#Kx6D$I!)~Ieg++Mc%U$+9>dlfCLqf z5F9@A2AF~0a4Q#AP23#6`Bls%?pRyNEBWbaEdjSJVHQ;2<}yN;v&3n&(xtmHJ+?g~ zkL1X3ey(|b`U5tPL~8B@sYKho25Icdn#X=8 z;8UgjHR1)t1Htm$E-RDZxP3oxPO3zReoZQ^SqHCu!=7^Hcbv+Be$ zsv8K6gv$87F8gJR`68PcHdjyL>SuXz7v{k;0N-X{PRN>Jz7XebEZTJz+7U5AlOkt!<6AkI@jtZSJrPAaNBU~*HA@tCEMitt?S zw-5)hAFw|P-deP6M!;d>+ z+lfY<{;DNgMZBD4P&- za&n1{uMjFA4D&kmV5PwZrzx^A426Bz)p%5_A6d(zc0C1z*8#Qrm%I~X1r-=Y4 z!6c#iN*@}wWnl7uN!s_qz~E!wUhgs34NH>4_)fqqRSQt< z%F9$|fmHYI2fJb=8u43n2$b#d1eiAfW&6UZ76`++HNj71ltW1ij`l|L2%JKwn#iFS zLbw(Rg`X`QDwE)}aP1m{L?+nP2?Hb7x+q(Zx@yRhyYAUd}q{yO2KvQi5R8qY@lc10set* z`-)P3)N}iueYS<4&oo%<_t6e21U8~F(jb7NOrDSh;Ri+QHW<1eks1&Pv1 zMLViNyR{CsjSL9cnS9)Wta9Jwsw2k9!Cr1&wP#P=KB<=NjH6$oNyn5@8@EJ}Lnac6 zG4zks*idDh4ctus)h~YEE05fbX3mE9nS->;)iO?(4V)?y&VPBjp!4W?ZJqh(2hlaO zZ;-0l{~V_F_1s$bIzJPtsc!5yBZ{~64b1jyE~P{Nu!6|4w3&&8$Xr<1)}oEu`pF6B z@j%|wL?78dRg(;f^)&s`Tiz)V>uFAIu$mF0^Xkv()8#9|jgqx|5_pw2oaCdg!)RvX z$+z->WAlKfW)q9%m`Rv`wO5?r-};({vxW^VMV0WWI;ATX3$C43vBES<7dk!$aob^s zDUexdEou~@%`{DTLFg=PW;Ei#t@&JWu;l{$>G&Ifzwov44M4+O$IwL$y=So<(b1z*bj6lu8u#HFyUjuMbgl1EbxTw8@%0~HBEfF`%2Z!J^)&lj2GV5# zDPK9c%Wn=2mtCelwGHmNg(5-u#|vu@Q7tPWhS+a_)J&e6v>i&CE}AHw5^n3=E%e-X zd&n63#`FmE1}Hf=ego*2H^Oy|x2$zh+IhriVzN?KxW2WLpQ$M1OW-3~Bro)p>t8()23Nif=g36hp1#fhrmkJ%`Ur5X>fm$zV3rx6A3w_)2}So+;vTKyK)!>cD#?j z6{tkb<<{~jjC*GB`%lxyna=X8wJ&A9#yBg-hF0AdS$7gNc?QR*VY|QadDZHtiQ1a^ zh{;}y)_=@9Qe?uM>G#9&SF4P*_-BfffGV%sqF%H-ou_o`&5LCcgEn#lQr9RQr)<8?!3>=hUETRbdN*SOG}EjNV0TQCkunBPPP*BUv{5t~-r z7)^Xvf(q!jl>x@mTaOgp{w+@nCDgxpSPa}q~QNbAXB=Ds8&Nm(!Tj&6rdL(#SL3Psa8kXwe zIW!DqtCxp(ztAsI^GRI0p^8P1s>aPGH0Eg^M~r75?av1L$F8qW-VIAQ;m87%`$tpt zqCAMvw`nl#zv9K;^HKKy6(;6to|>bG0Y}mh%Sj{fE}oZ%j@ePkKn1}03zQ*Tk$|SY zf8u-hbunE|mC)Sc>XU@YkSR~J*%9vJ6Uq*A+|2z#`GVRR%rPj)b?aPQ?F`diQzmyX zCFEWrVH+W%r}@EoUSkO|+sNRZdW1?Stx68@O6cn>e5U#lqS)RSW{+k+EJ}7PgI2FO z$*kN=`&#@m(~ANWdf|RI{^zJ6&}s$lrix;bk6pqeGBIW`IBR0G&HEhP&G1ObrCf32 zU+72+{*m)`^9Bf?*3D@TxEA<)@L(BD^IYWXfhOXCR%-c(8>FNm_!h6y?g3nu0rxk8z4= z^f&{-;GTR+k~#q)0<3(^YA!PH$7h@MMzyp=$H5~32E;d@tG!e z>U>dAu#HDBg}hD*uW0ZJ4ib@!4*9~sE)uqggjfd@;Q}|u9{kT&30Y2sA`o;R$P)?r zCK4DzuS`N(#UNf55}CtJLN+@q-VR@a4@bkmAUXhWLq03EF`3$w%ugO6kCPpvt1HGv z;AwOjTz{~!;DjGdhsPcJEo4fs;e0E86&n^z9|CX1Zz>@eBh8pdYE~2qW%+qrtr$*L zYbeYaNe1{JP+=0KiS|hWB4WG?N;qYfZ{0)w<@#K{kT=m`qZt)}Z|)}l;akvNJ}Cw* zTpCUJA;a;_%p4j{Ka9Y7<)Z|%D1t^QFe@t)K+`ngACla7Iij9UpZT|QG})Z6#)UBx z2`JYr6-T?GlD=B0xtqM4z_YycB3<-XR6!1`ZwGyPd0;N?Qb}d?r%AnP*o9JWL%n@!`<oXghF($eXRWM!#P(} zV;zncFK4Skc{Cb?OSjbduhHCv$yYhMm5{HOd|MkDMhA2_KyR0-IuCwrYb~O}-XRAv zZ*c=dJl8KIOFE}oaPh51w<)rN6ut3U)76YJarT`1DM%3pxScHUq~Yb1vR0W;!LW+a zlcgLB4DuEnYoK!R0)wc^C@nlZ-hm9oyVkX8Ov!owBsu_qNkR}&zKwY_ zO8OFgta6pUF+p9jdFc@*;V)6rSb*}Nxvexgc-V2>SKm-Z6_t*xq&YJlmr2q+BWpWl zEAl~@%uzV3+&<;#FRbSu+-#9fk4pRc*o0eJwhX%mf+oz>)WB0RPHvqCZQ09FY0rrK z)?Y98B^!PNa0WxYK;qjKjj%X_+hp6^PL{6cwcR$xxTndrIMVFYgV%tAL)O5Pvh%^G ztHEisiUi1bbkotOVB_VEBNXx<+!aibk4 zMv}rOjKw5fTYsgGkCT=o|GdK5b$3udVfeZpu28dlel$+q)r;m?sav{#iMUp@dUaR@cmh1z*?l<*Ky&WeKi_d-Kz1 zZ+oXX-g4Hl0I`2z=JWi6kp7dZvX=fqk}pZZk5Kq7!Sqc(q!|`t^;wez&*B5akA4n* zWsNTdWzyU6DMChreU_CQNcUcph=k7a6{lGz#r-t;K^?V5->L?ycJ;<_G*>^X&kFt! zyAXQ=*jmo*k7( z0uP%$>e0!J`JL+;4{`hb6ovFQd(*)VJL>5-RL#VD4bR@@B3$O0Cz0$D@ied5Ay2*1 zErls>UivwvE!>rKzbCnUTnU8Ch@{#Y>|B$z@+uHG@6jvUG+cg7G)*8y1$~@x`%lbz z$P28t&aC9 zQ@uTzGx9^5h=YOE000y(WeZ>pY97Qf#m)|BVU;XQ+v!U|9EcX<5w>pPNwg&uwQ#V{ zr5dJ(#-B0RRu+ZjM$G;`*3ckAEjYQzLR2o*@T0*HXZysjscuGh`8 zW_HoW>{`WnwVon}AY(U^R=5YWevowQb0k_;`n?o2-jclzYpnmBhFy2Z3W3VOD zSBswEz}uzVRV=A8e)&W;bD&ZDv{G{N?_kdOxno{mjOWFlV{>d*IN?Z>DR4Y>R-kdD*YizlrZ^)auRDBgsZ_pRne zqnZPB&~)oJ*wY6OGx}(w*;Qx=tvB1_7r;?2$&H!v6CuxyqUhG*3&)3m!8vX)N7ybk z{UD5r2j^*)sWOapsZ<%XPx9NI13ms*$w-D%{NrtlOmXrdTeOPH~Bbi>?gxTbYxh z*3XN7;vP!6!)wEQ1D3KX+LH_lTR<@Tq*vl!#yTYcVqG}_u!N2zk};>EsrqbB%}@MM zbs1C<6Pk2Lj`%FypC*mS)KL7`adexaH;giAw8hHo@)fUflGq9M>0vOg zPn6qKXL^=MYqU$1;4s9>v(#iQSWpBLgjmRc3Q;yoV3sk0?_@6b;66PN4A!5%K zyD?ZMiv`OfAmx<9i=8A9gCB!QcjeImQ$qRiP{1XXP8lhx{KEzqqD1Ho8_n7UkW&PTaLobf&C<4%Hk;1Ry0(lUojmTOjG|?nvIKmz z-*2Om^wr9?wouR*CT4Q}E!%&`M!R)-$Vy@N20-I5Mk<(#%$qM(P*phSW`7`eL&6c% zF0B!+FbES(uN{GEhT?)J*DfVw)-!Wa{)v8-VBq@QE-P4BWI!oWbJVA+$w$AN)7a-p z`seApm-k2w7i!N5*GY}7(*`YG%h~%TRCokGc>|sjy#`4O!#{*;u;e`$FfabpEd!jP!R z&_b_HWaOdz?{1%4l$@voXK zL`_wjV|6kB>_x&g&4BhS>KY2oHR_UB`bcfVWUL0Zb`k6VQq zPH_gJa*Yxh*1w+Qd1l%uy@^am@>;62%Ph09ubO7NXu^oF8kWrAw9o?HcS>5XdWQ(P zeee_nm&v{u*fVMP_%r7%C}=)7K-ZIewHI@P<6SR0?9e+|@d%_p;xlzk<-wcOrW0z| z9kS3x?igPQwIb{a76hbD8~r|H7ac6F{3vw(J;V&E{FosA=#?f7v9-pz!{Q5xVv>`O zMRST|Xm!W>Q2HJ%Z)sIJmTTVENnaH~%id9n$apm|b}(jeAJ$tY;QdMzx+uRu8*#FS zUcbBmt+sxb=;vbcLD7CHco#-1X?R^{!=;&Q+dVMexa{DEOl9cKLc6cM(;Fa;x_B8@ zxBE1L$QaT!ZT#UV&}Xnh(!ht=EN+w}?LcO9+{<^|+WU}aZD|WOEN=}x(k_V3iT7j5 z>v6!zV+dsEdbxe2GM6mEY39CU(ZUbVKIt%DG;lB2y!ym7qR$(}u29#&SG*R<8OzGs zwC!2nX5khrBk(bkKhk7N;>wG%8}^^yyWHg(SA9Do%(lxy(3_QbT?=GgP1*id@V#jM zhD6`lXtctXfewVvz4RZKibRVFK4gN(LQ{o&AGbF76 zu3&31hTi2plUP(K9!28^iJ$<-?*>`m6mNEJ89GMp>=GUl4lD`>D-C~C8CEz7kUD(K zrCc1GcWlggH<^BityFWxXKh@W^5iiGxBSZ50x%uhY75YfE(Zs4=*X2j0C{N+vJg=M5 z>sbRaKiPJ7-LeB;YHhI_krgspeF{QvKl*d_L$7t3Da5Q8(Mi||C#@J)EV{1#)GjTI zom<2u7~$Def$A5IppM=CJE3%e+mWuX+lH6%_$i*FZPIi;?trm!*@+uDW5bSDTOi{22V45- z>z@7SSA*lVhrjMAt%EVtHFaekjx`_LiW!s*)E(6}){TplmvnG7$|N3I;(ZUndT4Em z6wOHN$cXB}h+MMNIoONR{ONken6p_{ee2l}&5ov(RdeGtwWh6;pvwf#(@Xj_d34e} z{Dj69MJ<^|dv^zecG?0w)*@eIUm@j+oy5GgQ-PPX%^GSY<3E-gk-yK_y%u_xP;7KT zN8>h*31gW2g+yL9yjrcX&Pc9G)XMfhooxoHo-30k(kNE7_cgh@RG35-jq1${MiYb9 zqp+_V5DSL_TF_??+V94R_9zv~4fTA2nvBl5czk^pcyZ3L(B0R1wlS_iTQUzi7Nxwi z=QVyeQEve0+VzAJ$w$3rbn)#}!!kaf$u`gcz9(%+-E&1`d{wM0jyObhi&gVz^C`_P^WC^?q+51C1hf;S6gi6-l@4G zZLD-eZvGN5QD0_crw$`AGPpj#$zpG)))A8a*1Q@SwCZ`J-TMllTb|pw!C|8%b5FBixW;HM&o}R9nfHV4j2kodsDg+R3r?>J^ zrts5vFvHNDbt<-E8U{6>b^0f^mA1n`e5F0UnD$gdB(|FatJCeHL^?r@XHTaLsuK>j zA(IEc`$r#HqRJ)SW6p{c&)CEMDh$=7&Rv9wtRI~4plqWJ3@5EP`+cI0S%!!V>_{cz zj5%e1{M0J80)5BSG5T@C^H4eBh7RC9{{UYe%9tcmS`LwzX5Dk>q<= z#5_mo>duB4Z7oPJG!IX83hxSu4&4DIwf@8Z0K_R{e3BFHHw>n2q)fkAmbx>?I1P4tA z&=r^}MyfF8#vcubrH#YXm{7p6WChFzhCbdt#G%!Y8fYmwR1t#v6XK$PG7#OCb4HjH zaOxwwlG2GfVbUd_0AnmD4b*IUy_6X7TSFS&t`x0=G*SfB5Qa6pPqXsUx44TCz&(_; z87hXfdp$k1TOB1p&Oq!7Lx)&j&q@@k7Ht}FYP6~m^9Ac%ee?sun7>H3nYW;)F!1D3 z+<#XtG?LhCaeV!j+3mLjy_S`8~9yixA+z zNx=4$$L%XYjL>Kq;eus~`>9y;r%+>BS1+@!mE(}qt^t51A0N$~Ef7N*F&!R9*E;yWo?#3~`J9f|EUtZai29-nFWP#I(ol%nO^Kp>jXoKIjW>cXQ6seyPf{{VSc z)QL*Q1PN5Ybs~R728Kc0r+_Eaee{8*?8Q+>f{}LR$~B?qZbUqGnc zI?EJZ(Z(xqGcL#VN@v3iXi}w#A<7GEIY7a9ueSk5p%%w-9$>OzPT)Pc95Lwxtx6Wg z4UX|??tdu}lDty|_EDoQR>j}S9tSav$cvH>YA+fbEOlw-B$mOydxPb#uyp?`=fjQGb1#6c~&vnLhLDXOX=X;>+(pYZ^QvKSZ?u0BiFKLYE!L5QOpf zoPE@c7a@U8gcK;lMif9y4=-9LtAzj?0l<99 zoCweDpvH(G;(tW?cBKJ~QHVHjE7m%4%ApOlGH4{6fCQ7n0f|1UBp$4`&bg=%ZS?ut z!<5$`Tf_zhd8g%2oJBH(p2Fx@yFTi4^b;*V%ZrzCzf#m=fD`*WHksT9AHI;rz;fW^ z=rroq_G`DHm)n&(iSC`T_WhA*`a^vqP={W~Wdz}mggGG|wwkqCg6U@4dP^|Im3<`U z*4i6(h~kDQ8VZI-8)A*Q3) zfxv-*!8m+Qbm+A);weZNq5i4&o{*=ap=@Qmc82Se1bT2h#T}Oe$2xe(y-I{j%$w3S zefV*3Ta7%XYx~o2Qdbk5CZb=2*Ge*T3I6~p`q9j;ylBiksKA7Fd2z0NQRn!ClU%vj z5;$RsftVzKiYQbK)IjGFsBt(9avdC2m9ZNqnoD`KyDU*-JgcAWWL!H}3c5YiY_;;x z3x*M%ASeW2LFwR%duTFA3p5?Exi@v-(>A8w*0(MGBk43PGWS!sU~X=ews6WIji03V zma(~#1sGeM$4hC+^eT;KPpH*T!_z`{C%P%O>MSYzi@VUi^UW>&u>7(OQvDwLRvWu| zF%Nn#^nT46#pR{P0^7-LT+)TZml1Q)xcvFX9TlteD*DZ@lgiFK#`T>&KFfS_{^U5H zrJ*ymZ^Qo5{{Xo*e(>{eQHgnVA22mBv9_z;aOY;z+80-v{nRJ*XVu~=iZg8vEhIa! zmC0|D;a-K0rHkV7T{`?LS9Lcx+r1>FJuM7nE_P~S9+9#P>$%XA6muwM7hqj?mix!$4_VX zZADx3weddLGLX1@*Hg)Lb2PVCQ#ihBo0(rd#Lsc&d*+@}>7;v(B$pCSb(Ph+yv|Ix zr1+%kC$-|j9TQ1V&@(+N=uHX>KqaJRl?WE*6z-DJm<&KTo&&ap0M_89#^O=R5f=}I z3IQPQP=p{0)G3BE)-)6#^58fehGlU$(4Z&*_QBY-N@BPL3DeTNYEy6=Y9%)E1BgcS zo*4d-$aPc`g@OTiuCcJh*UwFVp-Y3sAg~$V0 z6is}NaU}*AnpCTTGpIKBQf^z8CFiui3|@8bE=hFVkEv;PGIEiqZg)qwt$=foxHC~P z-^$Q9G6y*^Oe;;j7N+NR4K42jucq?<05;WQZp@<%^Y~YM8BdsBJGP ze8Jp%D`t6ZweIfr&ZP#NH(vOC_VLv&C2!Ns^^oazl3H!c6U{GleJq89e8(!!a#r;B zovUz-V)K?S(U+*@>Mm)yZo1}^OQ*KwZdBvBS+|xnbc*ycXT1E!^Cxu8cVVq>4Q1L; zo@O&e)8>yx(BqGotS!+rNc8jW?>(?7nli}Tx^eQ7>t6i_uNy6>fcy;ct zhCkhn7pSd9n%q)6NgTuiq~L_1#D%JOOdc9AX|8Dj%ZUA6zBrMEP7-)@6gN&N2Pwn* zyjQEHrwSXx6-TAHK0pA(COC|<$BiBoGPWybl;epAI3Na%23FXqx3?d9j0l| zF0~H*>g}OEq_x4`3?O%fxOfDU^ClQ)n&rg)802!bT9rS;x82(J{=%|zkxHU2Xal=F zbxDG!JNk52-p_~`(NKm|36%(CLK*4kY5>lffij^Dv?&AC`6!WQDGf}$0@CN1VnBmd zjULgc3^%HeP}JTGM^mglL9J-$_GRm!*&5z&)6jOBfyio?_K*JCzr<*n<=$iVfH|FW z*w2foS9z;Ro9y?JaKYMm^-$t{NZm??hLhR-JW_|WYAv~dxMEw(aQ&OU3)783XWV^6 zW}(d%NAeD?KC+a{x^A`d&@`&)xVo5N1hsHab`>kwD_w+c_$9;oaKqFn9Q*$OFpzBl z{{Rwr{{XU2@lsD=*5p6de`S`VT8ULQdw53>(9(E&X>sf>QAjbd&>Dm@b~PVrcyhdZ z=vN+-&hy+Q4x%k>`*{BVz5W+)?Wl0<_t5XtpQb+QCgVv#Y&Pan#edwh9lScxKh^=#X9C3!qdkdgMs(wO5JiSO*{~A z6s)mefoJ(B5HYPqCqGYZD}I260KT7XE5Ipl$TW0e*+??X7;hZCI=gC9c43!?Dyl&q2dfC3TXijmwTs2uG}E z?4@z4z()oqk_Ry2!aC7G$D}C5X)7g!vMX_>WH)mJ`eo^V*-F+mI6U1^z>3nmjzY{5 zW$yIOs~p$`Q9Z*ig+d0lGVZ}j@XU1Uka%DSoG`>-nPo^d0fvHc0FjbmhhorUDhCFC zKophSH3LIO56e>>qa+&Erg(k6`WS2-U>FQK?ZtkAN+}5`d}pB>>RhBH)fc^?mr# zvBMh{Kma<6TI1_?8W@a_s7HVVf|>VGM@DrAN};W4kQx;59+^-X4vFOD9qel9R6j=LhYDj28K|-A?&_+1>sj0Bh7}FwIrRh$dMl}g5mUyZF z5b+#1QzcxDR_(bm=-ZnV-g1V*WG4a|(m*F1vaJxcQ>~}b@!*Rk{#4#J+nm_QTgt1* z2p|O*oV{HYYty<`heQ1~d^r_%;@L*g&-;@Z7@=Yi0^^9{I2z46bUF9*oI5B~*P3sI zD_hEhNYIFwb6{76W|U~obm7A3(s~hnWE#~CLy^NAfEaqKttGLZ-f_T)URnmYpzh)_ z95MGLQzT`aNYwB(u0V0jFA#eG7#glv6gBA?uUl(r1oxLSHVa!=q@LnukQp3Mx1G;% zE*O9^rF5qg!4)?crztu`Dx4dr&da?*{_Zi$9o6mK#BDi-PjMuy2dp*8T!}i#z3lQ6 zd~fdb&*6uG1U z`aleDLQYi(9c`!HU%0Z?%6q4|Ko;>nK$$C?#ul)3lSL;il}3aoH}EF?A)&;TN zENLmy0mrCx)Er+FfS^uXgHca59bqsP2TXN_LK6m{&B&z}BAC-ATA1Y-YrD;I6OJwg zN~ul>+fG=8O>t4oSJXkn)^JLLRgk|irIGtq@)j(zO?6_};%i?dvODS+jgB@4NcSF~ zYODVMEXS^sdS;e&LJfiVS@Y-2X2Ujzuys8zN|>~bYjuIPG{Dy)$iu2OJNu>vl?E6k z<{v7WuGWiHdle=`r&?1>RY! zdOOmLu087_it?6L8$lPnX$Odx3(i) z=4R>rq=nHv+BDYNO2T21O{!o)4h=X~sMv%eylat9bbcr5!Fo=`zF%U+wfXH$xXPW| ze~T`!hu~eZ51V`WL$(Jl&GN2Y+kY5$F5&ZYT9Z?au0RCPY@Mt8pG1kEg=;RZZ<>dd z&3*=pyQjDE;l(ZUb^idBQF_?Y)$-jx^tyL)Ywcc@fBJBweJ%9Vh;ENL{Lb9APtqGEq zH%Hz=)aAgH1#ncvabHr<6e-ZS9DAgm=qglo;nProWuz2By(1#MbpRM3E(5x%b4=4P zI&q;wP|u}8LyiF9)6;>eLQunQKS&hdKvH^f!z8IfP}sF{qHsJD-g?a%6NEHc5t$sk za3#&*!B;wz4+tjNg1;n5L z@G5_xRNd`pCXW@FV3efnfyO zlJ9VDYqng(=Q8r;x-ORD8zO;Yq`9{xj!d6&@|<{9#mcnsRhx2th3Y$z@^>t=1oaMg zWnHg@dLxglx^}0Q8!Kf(A=;ZuLEG`P6_-oXE$yxfDSM{AEOq18iF?U7sWqnEn^x5= z#V*6ErseugBBq+uAi5Gy)0o4%eB1J)Vwa84zV`i<%w+!IZFn_pRm6X@{$HjW%ci`8 zfO}dG1vHm6HZ6R~B>gYYigi5CmgxTgsJ#U($?;3js?KNKzm5%yVjpj^_J+N=<-{`Y zR(u-P z&2sO|4g0vao$ZybpLw{oNVO}Q8zVFAt&8M_Hp3K?z0%3b$ikOmN|YH_B2#y+%Im?m zQ^j7rEpD=UsihfQm0DRM{nT_Y?MD5EB8KKw00yUf1H+=VxMI%L%Rd~IHi@I#WvHzn zE@7mU=pz9`!x2~(w`_+ul!q=jq3)g;1jO~x;2-k!2F=0LFsxg0zmT zJ|N*!fs?@kbbeIsDd`E)ok%KhGI&8Ru;m8=(T)YD-HK3gV!L!3-w0{%Gy}m}g*ynP z5uqc?8V)}3%g~Bwh&9|x9|F;uqf-tbxZ+BhR?w}Xs%w^vhzZ9Yyayf>xs^E$a@~M? zcz|#Say?1WLa7xw4O8B=p~ccP07|%!8Uf-8l9tDFf44s zXRk*jJ|P%`g;efEX3VqH+J&6mVu1SUx}!UoiPsHNpA{#k9-5UEvX1!sEANNJy@qHR%5 z2xq6eI)pHEdq-tL8S3=Lp$w=%as2J=KMH|LtXLRMDTi(!mXA?=DVS(r35Ta;K;zVb z;nC62_D~9r(|w+uwE|*w`ejg+GvA-tjY17le%1c~+jqw->N70+%#iKCuelvj{7nXI z(XWbKNS8H(kEUEGbsY*Y(8b`Cdd8&!?wFRd{{WQp{{U3wQ`jukVETxUr$_YAIU>0J zM)j{(8fD!#+4*QNW$DI{&}Mu+ewkEgjE(9zTsyC}gGVz^zZH+$I~`5m<@I&dbI9v? zfBNt2vfOI|#+BcLBYQtOe#};Wr{mNhV*;rj-o<0fJa((I4^JXQnSu;=;H>r z5gTvYv)BF?C&q%z;92>IcA{=K@Z&+!fCESn)}mp;k>(U`mE;pCNC6JhO`evtK#MWu8fF-u<8e69ra zWJjwy2_dr%8IR4|L5{;f8J}+-RUkq9em|Cg>YbsjY6U!fl#SFNfC{Bg+1W}m9`rH) z01=2`iyz0O26YaHFICjXw4V59h&+8?mZM&S(x|E$F@?uG5fGnU2UM^liK5P>2jA7k zmZ{(l8j!8kXB2|ifLKX>EWj#sCoU$dlBNfr=QhNr(yrw4(c zpA3V_;mSeD*dE+PBftqd;tU1gygfT9ODKV{y@&=$fX9al51@-M&?#xWu^{%hp*>Ws zRfR_oV08L?YADcZZw_b)knKuYU@`^=Mjq-xfq**C_Hy^o8C2hY446G&laE5@Lji*t z&XiT+N7|GEgWDM3aOxy{M@0eYP+_iA!J{q&`iaJXY3L3Eh9?5K<=NLjWlG{28-@)| z8GcGy4Nbt?7(HE>8Vv@4tzd6Ytdp0nfXcf=K=;rcAzTe1?BRh>h(->@aHY+TvmIh? z3UN4(R%hK%wkUbEixGi1;s!+%0hVH)B~7LASdx1UhdS?R~&H_2c8Q{OvhYCy)u{bNwHv+%Rc#ehE;ef{1xj;u0UMXC7bDOU9CYOZW4;7~K=CXR#8`kaqQc|TUid|>Q=bt`Z zovE*C`b@Bh<*~cDw|M1hgA3f!53RjeitFEuT2hWp9_L?X5`w#Lxg}NFaE9ZsY*z}1 zk5^=amFl<=T*;%$UWFugjs2*RjFuDP#XK>~6p?`D$rP*)WQ}u6fos~}M^UbuTaRTp z)mLFu>iYJ9XM1m@-0M1fH7mP@TiVz%;ga3UG|nw!OfZ+p5Gk90Z(V1c;GYd~dk%Hm z)+04;Q(hx@Exv+--yUy6qH3Cki>Bz#y1moxA8jqw?A`^dWn)5^p@2y=t!X1SMyZgj zcXb>s#VWLMwVcgbxhpo|S;f+?FQ=gA-O;{xF6Lo}QoU;|d4q@{}qF%qc%@LNtLl z`ahzTf|vuPAurpzKJ#7WxslGZ(}Rfmg4LFC!{2SJZo%QCBc6MsEKU_|yHcJn2%79^ zC4b_0Ti^8-osP@f)DlHEr?|rzMM$|ZJQGV zB4lx}f3u7T+T0OCT0q2As)mT9uvr1{(N%qFSK=s8G%>Lk61X)mL7%HE4N3(^csECf zbb^@h2!|Xop+WdSHU_y4Ept_QNku>o2#9GoP@E}>n#2eYpvaK8(F(vHA`BE{po5nc z7#fUa&?#Xf0#}D=Id#w+J~agbfr%qN9=d^9jnNcQcX;Yd9sjxR|I3 z3=lE$%2bb(8Zk|}ELm(6r7jpI=K=SGL2DvPk3a#e- zr~s*GtuT$dPN^jo^vvt&ZRvE91Q5!iTL3BKWv9fZ)v)&yus9= z<{w3}Z6B!HhX-<&Nv_?#&q!zo6^?0CV$~rko4n6UYXtb7<&Uo~Sw^qTe4j~J#%}G+ z&|F@;BkMf4^IP#Zr0>1+t8cvr$^D(C>$;p)Fb#8giMQUV1X^voEiGSXq+h<@X;{eE z7!;c;hx4O@fvz?EbZ*^uVQwmKYxtfW881UT7cl4c^?Ipl?CGsW{E}Q#{6~VKq+KAt zy5C99yT8pJFt@;t9o4(_&0fwW5a}}BI@dzBX(#UOGz~qrdM%_tlig$u`)d`=MeDd+ zlWTU*Mn@Y{%r&~qH8*D{ud`Z3bwyGu&??d129;)=&>?bwnRt&F`sw4{Lt1}?x`U;Y z2cZo(w6viv2NG2m5yFERi9jThcoUx!j}8eyP%aJ_5PE@MbwD%}=m^a~Boeuvf~uz~ zBJhDR0J)KxoJi@zqZHv#jx+<|Dky18y1Ou_N*Ha`R0IP)Tt*#Z1gU`>LAC$@9I61r z42S3U)WDn|#j(qm1yF#LPJ~dQeYFF^!y9Pq2x^W8sGLxB)F%T6^d-2pfP#2uhf)fZ zV{iwu(aJlZH;y?2!y*-UiZo$R4HgY{ARe#+srb?18-O{QaVg6buMQ!t4Gu1IjscBh zRJ^#5fMQMprwCLJT0aWkgZmu=ON-1OH1tbb&CRM?wq3oowM%#|ZMM~n?PKHAwJSS@ zNff(=?l%J1-Uzga z&83GfW=A3FHIC;o)5E)2K98-OJx7`OWt=T>-IwsHFH5JYrzUxy_|)?YP}7WCM{DRi zUKd?4kzHzDF5%R}OC(1DC(>!LymiAM=&Tp{o#`*bijF&RJl8SvX1aFirnd`fxxb0~ zm#Lg%y8O@cPkk8~y0^yRzqfH;p=JBD{V}+X>vY{S>1`Yu0s`hZkTEsQxqAKInKsv< zdXFoq<~+w)hNkr89o3@rDk8jQl0%3LYCAV4%%w@ll1lo`QNoNW-Xh5KcMs4drvQy3 z6~~4GiiqmbE87|)q`vO4+`cWJIK|wd*DV>0l=S6RLO7Z<_FJE$+IFV1w4TjhQZ0?# zqVD4cwBnC;C^)Tj-@9f3LUG~5HNHU`_ai%%<~L(|GVTo)dyoFx3cGZnLM zv}P2H5Q1^+4Fu&!gk@m{w?$wl0ggezds6}`aEz>@f6G}Kq5^XX(e0^3iQ%LtlHL)D z08=MoN%aI$5~UCvVj-A98^B?hARGWek4*S%;i?#E@V?v z+X&{Cq``Avr&%`NLLfSanr}S9$4hS%edOem&`fZFf&SH1Ek@*HpD}-8 zKA+e^Z@j?VmqEd|aau9Q<+Or0^Uv=k@g(8WwN$T7?J`eWep^!NA=kRK_1Rm?+pCUZ zahiFh9>=(VF~bZhrzt$ymUm|^`)q1_;3^d*5MT#FlvOD}t;kCG)j>FtIA%!86UL=P zauUfnYgZsMDlqNnpxCX4Y1($Pu1w2q4@uQEi-#Uo#iiI?-@SM=i&&%}CsRoboycWDiF+lJ8EDI z3?7|kqY0S(iR=9J0B5J!jX;{HK-j-%(4VFD9*n969ppLz=)Y}4QjXw2bYs+qwEBfX zly{ispvR{#Oe#}u(8*v2rl5vr>FeyMLsbYh%lla}`Q7n76%4ZPGEKGu%W^Xuj6T9v zgEnY;(&|LaFb_<9FsW0flk5x)Jsz^Yi%VU@P%i2}-EKIa21ADq=`|K&-0nW25&1Z9 zqH%>${EhVE>ZV=OeXo{-z8$?D+CxUplRXgs07$4pH>rOReL5&Jo2mJO{>}dYy+61< z+OAn0UoZavUD#Z0VLe`+>Z`2!3{pX%fKaaD$}rFqx8dlNwW!K9AiK$`Z8+fXiTKo6 zo_n80js!NEQ$6cebX&qzHeKDwM0+{3J{S+QRHc%!WooQ9# zL=CwQO#c8SE6ihb2xb(IJOT^)I;lr6dU|xwVa$|TH2o|%DC8KXSfxzOw&Uvp%CFGt!xjiVSGxZh*k37G;sKKr1j{ zaUP!>=~(G)4~|J2`dK0eWADsTzLBv)mfSSp;D@AQJ!3SW8t)b{?Rf6EIQ^$A4;n^? zLG26i;Yd9LTG7OIp1ui%K&;2j1;6nKfEXjaxOCJtNFHJS-)aUQiD9}YC64rt+@qR^%TR;(Yhqtl8|7#J!$ zzSET>3PGH%IlG&q06l$ly+dLg&`>Gp$Ybonfhkbf*cEwr5V-sar5rRys!w>~z>cm4 zfXQzSYcS{?9BK?27&znYpgMI70B`_M87hJvoiqkYj4_LjD@en)dkg@c6rm1d3XBGP zRARkUt?Vp9_*g+MIpP&gTn2cID5Um|meJM*DIJPE%ZF0aBwC02NXIKlDsaP~;0Qe{ zMwMmpC7OF0ttd$)Fu?+*KFVZjwy-WOrz2yMJP5=%0*iw|%;s>b;hELbIy8DQ_-pfe z@5>#@qwZ}oX2E%;T)}e-`IEg1!-Au-IVTmZ@ld&K+MjivroywT(#uPK<;_s{M|6M9 zy8i%G^5%UC&qlVi*6#GvXFj4Q?v7{#PLYgGZe$7roO;%|@Z|R6ca6%hspjl?8%b+d zUR(ECcU0{_(l1HZ3U4n|Y4&+r4 z!@HfrNhBZ)2~`TXj;~d7XPM=-6l{jx?v2jPIhsp12C&UGG04jnYBx={WMrSZYg-`Y zjidBFK$&G!xz3z5d?smXH3}XV9K1EMu7gg?V()FNd_)ZuFKCC^s&?AF0!*Im6?LO*>fhG;C zQdc3E>GfuYh{*zUV4wg6P)Gn_f@GNrfuK{M=E1$u`b33aU-Zx#l>{x^-19$x{M*RYd^#dL2yKbP)Xw`%;ZN-@S5ww50j*VuI)^Zar7PoVg=`%=qs zd4qczHaF=ye41vC_j2GVH_|~;-xbM! z3d6O!da80Z`TTKt)vkEZZOuDN^8UfW?QFI-Pt~=L4dFSCrK&O3kvM>%(OX7A;%hYZ zcVBfV`rp;+Ia-c&tCf5hD{<*uPtwwl{HF?e&Aj(!_0TWx_kQKp_bt<|Y~SN+TAkJN zSnsK69uyPG=q=jPaBCXYw}2IcQ#sDtb8Vh?nQAZUH1EfprmC}eEOsoHVmVwnMr|Q- z1ROdCc;YLba1cw#2}iKk zgyVo$;R0!q3bS+11h1j0q3#=R+$XNK~2NpS`IdM|vxT|oX!Y2mJp^UI`$*;i) z#ldmFfH5HSj4E)2Ks}mlhX%wH@F3;H_mweBDDZ__V%F&|PHt5p6{8HgFhSX!9ufQ| zE00TYYKYaSJHIi@9wRC|EM;x>3L?>5N`Rp{a2aRaC1~)mm7!}i2b6PI1BV9!8|p5? zj{x2eq+ZPp%xQa?tZRG202_uXTY*X{2z6$d>^(L7CH@neYIM&wJj2re0D29E{{Up| zo8}Sp**@EIYSz*Pb;H39vVr5c5?6&R1FNIbDAM)C@oZl6*z!J08*V!7Qv6Ke7qj@E zPVL#}mudO4<~H8)k84l3wW8a*>G`10^eClkZ}r_p0T9FwGo!V)FXp(Ij#!z};?Q^9 zy&F*Cmtx*}J!ZPrj?24q9m!_iML^^M2ohUnBccI>%z_N#vGSen+BTIse- zC+T$EHWK13Y^>N(tsgNZ&Q~~^;oNe)J~`t3ok!2LQ@yUf^%wD*^d+$_h3n1YM26OU zWD>_GLILaxk6kYVDEzxa7Vq0cX%M-QC-0F1%n@2*n~DH3&XVLl9@3pnhv6(XmGoP0 z#7<^_iiNENs^GYgM+&FIF)I}vBHL@-a~uvzK|$DB0jA&`SQgvuS>8--wsgeg*0IlX zfx)f;pqAp4Rkl2I?ckM53u$bt%S+KM)s(SCBdpsSq;DrPh0|Q%7nRG&)5FoZzjfBw!9X^ z6dff10T%+&g!1&{l)MQQJv?G-`8)xjs_(Nkr|Waz=L~CD!AcN4tHc z+c_i+Y>|*VlEm<=k%s_SdvG{rC`mZFFkMd6xMi6XxzyYGC08KV0=zLHD!QXIt|5-& zQyuI;;!uK0l9-f2v>Pdv!a0Ag+gh;6b9V)!1||11vM_+6lOviF+f})wXPxNB88o`e z_f+#cYFTrKYVqAj0v1@uCDFlDHSw{~oWEHys%3r)X^Q7C(?ji5^kP?9mz@@E4i*|+ z)yV||?xu>}r$6>^`0qj1l|;2GVr;{6n#1fxzOuj6yxQECSX&Dl29cC-_|mt4`^nz+ zpZWr-dW(-LBNfhNeqGu})oT4~8R;`?$FyB|4U>mC3x@FLHl{8#ejLZY@LeB&PSqVdFaCrt{mT!e49 z%qpH4U{e*(GCF7oWrrghJ$BcPqy9^eP@`H~%+6NkV zTe)-P#lE8J8eLYU(@$=5>Q0`Vi?>4jC3(B7_;1whv|XzUke}xoqb@Gm3X{!cq%!@% zz+|rjq>9rXU#OmGMRdQY>sj*8GrNt&3v2kDU!pZGjR){kpj(#7x;L(^s<{U}u7_i7 zq}!8$Ep5%=)Mw>Thqx4UR+#e0ubC$Lu=>umpDEdnI*YQGL{f_My0YWY{{RoZPVJJ% z+U%937q<^l zXxqbQr@}3?+dDlz-HiKde{XOCxrJy)L437&86x@FxrS@~!+ z6VpF!ElnE@R0$l^ziw$>XERgtNBx=q0D6CLc5$SZDC+rt`tHVEOaTXA_*QvT`Yp7Y z=?`lO{HN18Ub5OG7$XVO+l3yXlBoyYOz&DJL$d1T%Z4=;3lq-s)r=}bEnD~O9DCRz zAC%N@1BDd0hl0dBX?TkostR;tK0Q4(DkHC8NoiCn2djrgB}jq#dMzNv`#n8orotm& zC+GB91Eg<03DHSRF%CwCbT$mLCDu*-&IA2Vw9#1`?c)R)j4nHDS+DP~5+1~LQz^fU%Y6G2*( z3Rr_^!;hy7`srFNSp#SZ>B57^4dMwh^!V|iMgZgkAcw0z%Syt7qn=h^pn;HfKK#WY zVw*zf?wD|REj>B6^3b7TlG;gj^O*fg4o3$JaUQJ3Vxl^{u8&t5W0_}oVEbu$&s3t# z@&E=4hG-Yp#VJ}D#{dRCU{eL->59_q=qsb_A)BFqGTle%IIk7N6G~h^Trn*^Lvi=w>nH2 zI8)3<{U$-)h2h|Ee|6b#AS1nkmBs7>x41z2d1G=)$F!c2h9d+xwvpg33t59}6AVco zq8E?H)kyIQ%rrNoa`XY1^`g)$4Qm&M9VgIGpkkKL=CBOEHXT$j9YqEJhc9@bGE@!} zjEEHh!88+<0g?<1Jzn|)WWxhMrI5=U!&uek0B|R;{j`~eh*r>vf*f*AtO-nUronZO zkggy}OqDRhg%+?ZN`tMlN`g>y47x$$NAy%hkt7zt0MLkdlo{dCo>WnMb0qc-0T&E7 zkUvNs(5Xclp^;%|*G4BZl!oHe2PGYt_SLEFH(Hf3MbEdlJMBYDBrWM`rb~dtV7(ZM zz7wI=v8Zv#pB?ifq2=-1Fw2?4h9^F%x5Z}f&S9BeYuky}LFLkyk}$-Flh{oXNf?LN9TqUeAw(;WU*Gl(mN>QVP7`)m1pQ zQ7dwjtq0wSp@Y4)pO0|of5$A9u{V@{(4U#)l?j>(YjLI6*Lil1%KYW7C-%@+nEHLB z8~1i{M8_8EHy!Ca=N7XSe=|`}MJ@D|*>#g&2(4w>e)}LYj1Y7j0Q419jC%$hM%A)H z`FTN-AWX_`F?BOmkNSnw%ix4Qn0gA-(b+ZvpULd|XK;j2d z{(4bE;M1<|5AOEUIAD3h9MO>&Il-fkO)`w0NO;?nv5I5l8(UG74;|3J!+~quWpEkf z#S4cQVN9({ZUyU4ardZ@`)Rc7SQ_c&nr9VOwZbUL;gZOGsZOmerP0&DXHoVYCr`I6 zG1$V=!Gxut$`vsZNUjI57MVsXBwVe+IV3qTlLoX#vIN0^EBbI_d)~?=sWv)8rs@)e zWu?If54>PwO5_heDIU6HnQDaINtKVAyF%hkpMP-M2SIS#ix4D@tSomm(+A$Mjw%o9 zB95BRskd8hixZ#B^sVV-E*eUx;YrN>{hTJS*ti1rD>(L(07Ar*+zx!mUomW z;*2Ntd1K`VEO9i-+AWTn3p`l$28jekVYiMWkI5JcnR9ZqcY5iROI4(@Nef$}nE)XJ z__LK7Nd064k?EBlA4vWcdc|zfpNBJX2tz|uCJI`3gVHGQjI9W@jl1I6$#7ZAHM>KU z0nGTrfRU~vG2T|HqEM77swLYMj~3KMBGOM5@}UJ$N$5~dLC1wB3Mf{G2=sS56g=4n z2KRmgjUExzrY?^}d)yjXh{U*u09rU<#8YsNtufv$yh;%oH~=~n9Q(Ny>7&9}tn^|p zi*CG-3mo6pXlN(315*U1;4D@c?-to59M&OdQc9G=JQoBWs&5RjY4;t~_+k7oN2}ae zd5^y|@a-DfSnhVtgLxl!SGkSg$E)gA@-ZEyMDVupI3FFuoxsKbe(gS+{-w^v@A|zb zpyiyZDw?fI>5O0apGM`kH-#(TyZ3l#eqHU`t--EJOCo88!%nwBd8=QR7hBj_G2b)a zo7{V`uon~pw#rH?iR5|6&y; zdDHY=I6OKnKE;o3C~I&pdGE_2x!9$1rsZ~AO!E4NcUO1Cx!AWarf+E-gJwC%_QK#Z zb9qZtmG3vC8dYtGw4DR}rpJBA3}vg{N}lUn;(Hv?f*!0X)vA}rN5zE(4ejNv@X_E{ z)H5T8gfDaR@qB3a!bRrFNN>zPaWW+SAE(*1M;P8sX0rGyPPk4kSlw;}Y<5DOAez zBb!DG+UpSSPBrxV2My=vX3^F(_~5d&)U_L^p=0c?1W{T-*Z5&u;!Pou*3yg*-JFyK z6mDv*$zYpf>iM>o>?s8)Nh}^;L~_ot=T5rGWP;mGvD3_MEpWJWw~~hd6ZD{ns45RC z8pADA@<}7k-R8HGYB5oLIF-hy<}T~h1BANW%rTWbmYkMs#}xh9tab^3Rq3iFdgIz* zxvtdDIkdVk_7H2WN^6&M%{p;8#Jem3P;&@I zCnA)+WllmVE{E%7nJ1}03B!d-h~y!X@4oTIY8Wk0>BjzYnpk62Qr7cBlO6l4c zjtUP0?%-$&*opHLL{DA^u#5;RP)5Yhl!3!O1wEy3!lgxQ%Uf&RN_N}n)_0nP#Dyca zu(qD^;pCL=#cdptJg-2Ylas=&k};jf!vy6m$yeBVxA;=qyN^Ngm{{xDPNkw<&AIiP z_-wUnX{@JxrOb9d4akCKwm}r8Gs;K`mEO=saIs0IpA*dbN0#hgv&%RyMo^QvCrK(g zI%7km4V;Kx_oG{jh-ocuV={p-t*3W|=H3N)xctfT(`q&2aBq#dr^?mlw|1!o#o;Yg zV{Gh=&DoH21}#%N_AU41muZInznAHk<6eYb>Zbh)te|%<;_J$7G`=kw4avFZU@hme zux6WNUNHyib!Wjbjd+NaI95xX!%n-rQhtwV#=qunTWdL=*^P$qhODiF_1{{W_;18x5RZ6Ef96`1zE zV(T9O>f!0ZY0jFC;9-lxg+`)4W@1LGihj&tl>y6PEc-ej+6g}AvN_Dw(as55^r@5`vLLjddT^hy}*I>|N|8`bICLj)cj z;a6<^(&PM@Kf-f06K&uLufu&sY4*`LBFMf*;pnI1OuMH03*bSjl!fNKJr;=w=A?f7 z`u@sRb9Fy7f7zI7r~3o#q~XTUIe@~y^or1~6gn1hwzrE6V}SK`(5-rlC=%u=#AI`z_n~O^5uL!)Y#-ZyXUYvD z++)@(B1T0+5(JdALxTg`Lj*Fw8%7?U+6=K|7|@ihg*t+u6Dkl87>{qF&}@N~TL>U2 za?Esrx7|U@aLO(95*@h0HAqM<1s#YenRIcgvvp@vm8bc*?=5D_G&Vf3hyB;YN*J22UzQ*JmSbP5fHD(*+B&cv|&owWhxxjl0pFKKo-Arr&q3!C{!4D zh0E1ZDD52}@H;;x209K54rvYmwB`yp95{@r9wYFHq|&YALl`i)gNr^@&*aP4f|0~m zYA-AVQt5XL3K}MBaRt9PPMFF&E`_CWV)#;7Vj?YYJg!!_W(YzPsDec+0$76!!_(1e zSz-hM_x}J-RRD(oc+$`)HK1dH`jmQDK zqcTrd=So+I$xtz@4Nx${Gsm>lpy)6FP6xFz%~Ov}3I>&8sL&NpU!lO%kznr*K?Vu$ z9h7z!M0vF&Cq{jvFKYX!BISc5HnI>wT$Baq>Ge_CX@ey;R{$b7aOqVjIz>8Wh&nt8 zuA3#)zz86f!yxv8)&Mg+??zxc4LYkfLCNHqErQPR;CR;!iKjNTrbj0bM|Ga_Pr}|6 zHAXUdu<0ypV+$af<=Cl};jJYCD;OF8_2ol_EXLr4D(cwRa$$``FtR3vQr0!A)Hy&d z9kiG?fn2GvP>kKXnZ)wBK4F@oV5qfQWei!eTu%4eoWTD8- z{bj$thTk*4_V;&`Qgy4G5lZr~D8XFd{+A!#h%GibmC2Gq1&b=GTE*?#YhEk=@_Bv_E4b7 zAyABD15ji!_+lt9u(#W=Vxgm@c=i)XKv4%prC!`G)7xB3I6O0zhotkkwWvB)nI^_o zE`pI9-!KGEyh`o4^VAcoS#uVaF5l9c%-jF z^0V1f!+T33rIPP4_S#0Oqp^aGw^EXAkpJm^RTX|*% z=T1avW(Mhq9Oss_0qaVt^jo&*W9Bww^pfFC;=QLDZViiZJ^t z*O{HugGL3&Qn|M*)#774^1L;j=A00Pz=Kq~ zYA+Kj(<+% z9wd~=lT(3*z-U<(isQ@N4joOzddMb^0P4`|Z@2#dtAXG~Jw%WSrxm79EscTZ9laWN znxvD6%miHVOsTk0YOuD3YFl)FD!u`Q-@Ct*hJ_g^CpA23ID9NKvG{MOg4=Dfb7p9S z1?cTU7pALFF-E}eZ?)bzM}y3~*^CfkiI)mx0@bH`hx=`;;@17zf{Hmxjy>Gada0BP z6>T9T6Ti*)T4IbWB{ozEN?c@sHb64B6lj4!LPo#ue zxp7r&Tl!x|M%}n)-}|NdX3)^CHQUs)(=20bO}Bz9WtBNC>~#2A7{|>gAPi$lG%3NN zv?)~TLgy1}J*S+T=U3MJ#MfV}Ydn^=w*2vFI$@dax6>^gQO#>wt#!=6b4xA-?&xS} z70NDe9E|s4E7Gj6iphO8@A`fh$?>y2UE(IlRhswHP^K->~O7 z&9o}!$WCR%0f((Y3&yfKBZn>(kl8)#Ko>{K91}bTZbKf*S1rYEYhc$lrl6k@jdoaI zNa$15OH!wE(lXs@LPn9|I6M^`&HxTLj^5|)+)iKkkF$Fc z>u%pNPEpPvzJgfLev=&}P`wL=T)0)Aom*2jK6?s$P?dVfZngM_x|NS+owwqQm1uqa z^W{#z2Hla24qf9(En|JuWxLNL@V5$ezo@D8Z#Xxl=QwVs7M{l{Hp1E{To)nZN#jnV zsljVBwHF@R9NWClS1hH%x);%vuBor;nw*|B?xTHr?A&gyW|tn2*U8E~KvgbMmTj0W zU%59~$+yzT+?S1R9eCt%;h6vw_flYIa~!mt$@H!kR}KQ!M;``9CoQy&QOZaPV0k&= z%6h1&(+81dtNcF%yS~9r;k0g}8I~4-cMDG;9H37aJl+eCW}hkP>NIB>s_h#5d_}~& z%hpx$NbYAeg3<|TYla|<2q6LxnI&gA&4}ccpd3NR5%1ys6*3XXZN%S)T6`8Eayo+*X+P=3(8s!^Nrh! zrO(+&h_8Pb-d^pPU1#OLmM?}jw1}meJu7S4IDm|M?R2Ya+o=>GAweyereF@*9L=6McD=DNwSvQUyM&TwVx51W48X+h2`A8%Ji{)cGkGz``)o7=;jUL zBLo~lth44iiFauprS+VP{%PiSYOU~FT?(($D*Fl>aQThD_gmg~9`f9pv~rSNZ*i*K zHq8^|Hc-ARGTwk$hYHI(*RFTvC)#nZ>-D;ypLXPy=G{FR5I2It;s943@lNq4^ps+z zsxkyQq`MR&70-x6)yAL#9MIB2C?f^?a;10*@DiNQZ`(ixS?T>1`T+RZnEHDC^#OsL zeKM#MG2w<^lAsLxY6RD&9hC@g-|6kB13f)D>I6RAEb#TbbkxXuUomx(AjIL1pr+3= zt(Y(}qd}BHt_R)v$J&0{jZqceRJQuW29U;QN2kJ}4C}+JR3Oza?Iix&z9%XsS$CN) z*`aRkLT3|xG_u}wId{yLQDcS*Oj+5WU~NRbBPfL3wQX(({fwXCIa6hbE-%Ac-g*wrB4Vk(Yb?C)HB8beu@9i4P8E5lEQrZwf7`K^#Ke8s&lL;At3XMTk*q zXX)#uuJIgekXc|prOSurs47^6mXVHQtMYKD698VSRx-;FX~WysO36}nrZl@xw@iD5 zdT?=}g?dhwomSEp81e%?96hv{*QC0hfTy#89UX_W4mB!DZX%nKGT5z&z<7`K_h>T? zv0%E$qEeWW28aizrc?$%fIS}C4UV!51FDsZmLTm-;^y|ifDq?u0!&KRpMziPC2&VOEmU)KPt5 z1tkcnvoasMuBMJFa_(72HUZcif2;)wNzn7W{!Lqk>X7K~VR7&CMo$L?YM<*gC%95o z9%W5FvBkWv3ep@1Ug-zgM@0hFim0KiL*Djh!-&g`4Y-c56#x-a*@aAo$T_VzaKpC` zRVyr8!`jio0v?P>%b{sTLCj$R2tYdz<)B!D+kj3zCll35$B1)6jv;uA59g_{G{oEO zpqZ&GZIgnyVi7WMM+4YtUr1b2%?+~Xk-Hmt3lHi^-fy%cf_mv3R>6EP-5X`Gxwlg! zN0UDho#uA9v@3mJQZ&%65{_su!*>va3`%;!0!=6qsB2r$GS7hc(4grmiUKAWc478V zbQ=l+Zh0?9rgW?*8ob~FdcP{tj0iO5Bdn2=8eP^bY< zT>*zHe6ctmU@O4&)L9lpl2}+=P6L|GMjS)rabP2WVG1L?J0;O7M8re91%3|d(uf2a3s7t0y+|%C9u~8803*D zx4HlTUP61&bYwpz8naAhF8b0N`(T-%#mB2WPBeI(3l#T>rMnoC@>3=FmBlGru0V4` zT-O1_lJa`6Jj8*Yc~DY35nPzjb3O&U#atD7vZN8Q&ttuJ#?;3xZpCuM_?UInxAP;` zS)<2UZ||>Ylgz{$`*vaqYgZ(ku|||$7@A#2&6fIuU;SmzzRK=Et|XyeuU0igaxgsV zHC{$Vozk=tVsd1OfKZ*aC6FiFeMB?Ecj~xypUBfxyi8`yY0^5eKV=FJLUZNIKlP^6 zs6V>j5T!rZ5r%m7RhGNx*P>s{E~a#}8g=c~5L!(rAyuq*A})O72ItpW(r+WZ8*`RP zvEmJ_eIj`7hTI{*xzb1h2NY*?Vf|`v4q3A3>8A6d9w6FQjR4Iw)1D!mu(yE<0=YeO zd^NPWX`%M2$7pnmD`YU+K+@Dl82m>l1QJ~#C3_7O6tY;CM$99MN}}(k=RMF%iX%Y4 zpvVL+9@;b?hpcqA(C@As3vC>ZA2hwdfBNYJ;5by^W5&hC@^l^{+7d`od7A(Tt&Ogg z-8s0mgWpHOE?QT$(0jMqntUh=A29_K=`@W7|jtHG(1DpF+HuzD(cJ61Fk>xSW8P|zo-@$&3g+C7CzaIN_?J;%?kxe{1+3+ymPqKi znz*se=SPIY9D0_ioG?`}-m`+2NW~6{6BYy6LD)J);g9PkyYI+{Wi&7|j-3b96}wQ8fqzJN<{i9)U* zoDW(c^=C|>ytXzl7c*);L(LfFBcvQxEYtuxX!v6-GkFWG_ROs*BxQttiWE;;1DJ16l)Naz=b5D{)k$23KvGlK(_C0uAO$VbA7{s1)!)YiBL(;s*8uh#+8r5 zeMC~%Z-$OmM8wFo1DA$8qb8qP)oKXM8yk@E2H%aS3tB+`iFicNnI)Q9#A^O{G zT)iCytgaj%$;i%J5SPiy8mFR}L6=8QC*4EkzS(mJJ;b*5>|XG3FLIEHiDMI+6duzVq4&)9w_48UroK4U)Q5?41hZP`HBENzpWGMzj*fvM8Z}ftI$m zcI;P4E5|D$iM{6%0HH?n7lKlM6FlDYN6rm@%)M;fO(4^>I~U6yhorEAEuiK@b#zI4 zWz=q(2Rr`&L?au3#Boyyn!*2-s-%Ix`o|fu4Cl5UG+(_cM^E{E%g8-mxjI_E6#x-$YMd-&VS1tFEr@ z9K>G+5aHt7ybKu=2~u#)031qEPU?!QGhWt4+<2t_0BOv-hj(7* zZEn{R$eM6d%;H?(i=0-mPUDl}XP>{So$0y1_z^3eU_Gwya6CaO2k7QpeRWfjnpa5V z`l$rs=M2iYx8M#4XvZmPCJc-QgKqXFG zXGmDH zKa;tQ%x})!wb#V`SbEDR{{YQP@bl&K8$X%8YyC{ov^q;UHH{Z;M)J{*ErqY5J(llmuUEMfn<{%zu+V(C$a2`J`v-07@`m5_=zGLN02lUo_Nv*ss`seNvV6VLG<$ZmM-1B5p*`!PV}4#;H%p67vxtPt#F4#KT=}%JwFH+>)Tm{@=Nq)) z(pZdR{7t`7qV=<%_b=j$&kf7s2{)z2i*EJ(*KJx_Xsl>IqAo4tefer|GZ1%aWY7or*FsMU4KP^BRP$qhPl?ZBy1s1==L;hoaH5O%-er$TlKxyDT zf74HbJ1GVRVbkgPC^-h0;1za0;N&~a_u=lPWmnZLy7Ub{pVK-96F$$gwxJC4{PhSm zOZ!Lv0BHP86D+&WteEV-<1RnR9^*+Y(b46fHcOzhIcU)CsD=ifkIlf;>lq-csQ&1=2db!O`eb8=KxhQu z1q=~lh-+GyoatMtqR3?$5Y6B7T5#^4x9q60I6U(iTOxM9w~X{Jvyl5KUQFUcypZ-n z($KZ4fGk}7wJS`;aR6AIpPr^>^b0jA<5 zx?A@a$UoY+FIOQnjOXiD!xz#*^8BOvC>WB!P{<2_CjfB+iS6JjQH_A8M9|aS86LhI zIw(;QW)K_0FHf?PgI5d<^7{HQ&mOT#8iEb>@n5o*p`~&S5&UuK{L~)t)G!VaE?s)h*5hFE3Q?V&0sj%CUugKsf4C`nE}-ik$w zro*Mn7xR%U(#8DQ`ub_;`ww46XoTVG;X$fo2NPVp#Sf>VRd`aUt;U28dIYz(;*U1L_vJm;7`RDSVYg(DE19BQM6n;ua5vf)i%778; z9-UPvGQoviw2MoEaQmlp;4tXzq?SRKb@dTa;nYuIs!YjH*bY5I*gc(f1`Q3%kK5Ir zOG!Y|p{*G76zKfa81xJY$KRC&K&ovHYg*t4_aW-*sMy#BwV;&8v?t_1C{$$Fdq5Zy znCv8b=|?0wMYoLeB)G>J2nfOM&Ai7DS^>tH7ut0LasS3G*c~79pboYC|o#Vxt^31a4(FI=@r9}6;aW{+s36!6vZO%q#nh>gJUUI zFb=qJ2Q|$}$PNG?9N18Dra`U(cTLIxAiJOsdG%6eD|N(cmRC5EK{)ZisXbUF31lnE zq=#XX{q{`&TW`I(OX-GK;AX6N;BBa<8f2}`K? zvg;o`CtnNG*WsnG5jty`a<%@?X9=eXL z22qoPqS!D;qTV~_F~m5jE_YfqV4k{87H3vxw*kT}4i=%2@<>+@2Ov%ZyWYdLrvY-Z zmiAh{Qt7tQ?<+>K-X#_~i9F2bckt&DWXA)Dt869WVYuk>$Vc-_53>y+%Qy z&I(5WxYpcqJv311Z=q^afMT_qkf(ZOiJ0Q4BbvVYei=Q5v9&p5od=jUY|;R3CWahF z5eG=*J`)K%mC96v(n;ot8g@k;h_?He`r*ngPK3MreW5iQA;Mt2Y^t`EVU9glb;0&aRE>olzWGb zx(eB*I}U6Pc!lH85j|BX%M=CCXzqCk9C@&A0}?_h80VJ?Dhz)MX=PY#(`O4~5eE>! z=blyOdyu%eAu1byTmY+-C~k(Ifpm_s>1aSLLz_!lJ$~vmELO^fQPc?qe#JY%X z;m`|gU`Cv78lxadjr>Y_@~Wo{47cpk3#^Vswz=*^wA zB^nlx9|NOtwQ&HnfaaOz6)_|syY+6Hn|VRK2B0l@gU%> zI4H)gR@t#>@x*ChWnh^FO+IUys*IJP+Xg1D*W7!EWV!yJW`wZhZEP>L9~)uBT9uVu{|z7vQzDIN<- z0ag49q|Zwc!xq5}a^3}Po}X`QFa^#W&ln*cz|)F!)Z^TV<3>H$q$8VfT}jOwZagM9 zA&wCoSdan59U;RE=yA&-UHLpKfcv+0{iWrV-tXL!+}muuBH_08cOl?v8s@0>c(&Vf zL~Nlay}4^v;y|plgqOb_Q0S@Nmvj(Ne;vp8YuC`ei>+eRYjW>fz0ZEzo0y=nvb2e| zT0V_u_j-1VrbGJ;KE@-DD_5FFp8V1h_Z2j*d{3aRciqWV;I=K_PR+?Al1cS1Fm?S? zNU_lMO?LZH(ln`EE}vm_bot}8k6^{= zkomU+aWs?C@1%%^qvi&QXn%Osto%@<2<7^R4k5g-2@%<0 zf>m<62@wg&Sz`v zsXtKw)V$QyX9c!!TiH7yBa5zPEyUr^$reA^C~BEslgk4Y&SQ1=QeLvSz1ObqXLZfp z)VC9r=8{*Jy!3_1jS2FE6;qRLt>R&w=}qxT)s0f>WS(G2Jp`z5C=i2yrKwxFv}=<5 z2NE;t$x|LYdg)w>+{8272?B=}rfnDln&J_9DO(ZBqfC>KNV)NoD+>HQO-jaR~Cp%@W586a{X1$veN0m>*@Tn&NVozE)jcX)O}AM zQF-Wp^83`S;TN0x%zXzzwP-GyEgr&K*E5iFJ*QRHrqV3{OjnTdfsw5-D-UlnO-*__1m-$DR{{T~a#<;d|IeM+(>oW84i{u{3)8(@A%gQMy zyta}d1@rX18*D`}Ylt$s>CDvinFtxV!IC?OsQHeP(xr=d^(FPPOU*o&Ne2#JrL30P zMjpIn?u70phf=YDc)uj)7&j&02KUq1cDp32dB6%kfEbGgrQzDJii(maksd}uv~OO|}ubrzcdBOt1APeDPput$72HG2B^ zdSgIjg6^aJ*5ftsUzZN)=mGoqOY-lhCO_5G9?&D8wC{{Uux-k;nUb$6**)$;!U_5FqT z#;6@1kt;hspBI8lydKu4<)kc0gG@bCXgw&1KJNLgIyq0NLjc5g zkNrc1D>!iPDvy+<=EVjTzhQ~#`6+Q(22rR}n7Lqm-lWQneIhl#Ad=EO)U7VjTJ#0E zp!Ie1_R_UN1#%(F4hIiGLc7G0s_8S7dVQ1`T8k%|Lx17+`QX#qfyeStXzOX>{20B1 zt49xCRaV(?jvr^k?V^K|p%UFqTYGFVGP|%NI;u3HjC$0#BKk-`V9T>Vh8=ZT&m4mg ze@Eq`Mihg&LM}OD!x8EKF#KhocXF<)v*+dboQ`RyxD#wjhL$$uCGgHQmvW^5J9nryA?i6^Q z(&*fEq_G~Y@9m@@bNFsgmQLGi8D<|gY9!u8v4fToO}>IxWwG_klru8k#f22y zNDPQ^Q4~HSgOjARQ+RUQf;B@)dRfvdq?p+Gd#A<9Z*J}`-I-)zw$U#fR!JN^VACrx z&AOVSqJn=LZ`4k7jjw0ow%$e*wvA?t4OR(wzSZ#Ekd&D zt0yd7cvYq=q&z8wzXTRoZvpG_U6vbxrH}Zehfqdni6`bt2b_UV(4nMqSfkt zO}RC`lwOuv`+v$8Z4GBj*SEIx+d31p%gAp=lMUI`H5vQ@q5{isIXlA=(Is6CH-{xF zl8U3tY0|OE-j?idYVO;Ei?0o8@aij_<6))h+qZCP_IFcVS>NiD+*-V`*4*&FkTJ#= zH^pZ$2Q(;hs%}Ozxa6{I!>YG#?xuHO>jx^KqCGC!5DZrF0#x%ks_wq?i6owc&a2_3 zD>9{SD2GmpUfh_pIBp*nI}zm%Vu!LbZZee@)H zs-+ojGpkn~NO!l|vycq)wk6C)!y&-p6!J7>np1dn%(^;xEZqy2$^EZo9`myC82SGI zaMZAxPEcL+&OnidAj63!uhYUlCj1VkE7QaCYPh}^>^l}6GAJ;&w}ugzbWCeW$f`c4>*-Z^C46Rl{`mZ*xwlLlPC@C4w!Bw)Cll_>DkiUP>2 zIgof?-f&D%aZWuHcuQjn>=(sgLbMwac(yxlQJDM9yAba7GFNpcQ`eMN+=t@R?X=A6)VQ7P~K;a zt>>Ye7iVbtx0jnixAyEWZii@Nx@t1w93GsqfJtjl0th`7(uF)Yr@0=Q%wqKLjFNPM z_6)kt#Jti=rel7$O%5bn!x$4rF+p()Nim{)!CTiyg$}JPo!?m>WOmUYfJXFZmIp8@xykUeE&HE_=m`Mj@HKokgDcWDlTkHCDLO%YKA-TZgy7S; zc1kv5oA)lCXG>`~CjR_EX#0xl)*U(r*lo=U2iR?Gv0~Rc(?`>tA(8Hq;&TIUwQKo) z_VVe#$uy+o(AJ8jsDsNR74B(ezlXZANTqczHZCM-`Yk#p5~%yDhd&H~@S+g?;Krodv$OMmkDN?P;*=!=Kui*g(H-nW@+A(v6F0ReefiJ;utpHogJQV|9dRRyu2^x!J!2Y9 zBEBXkl48(Yq=iBTYlZn0U)k8uVQ1GR1`q@142mVwWvwvC^28sz+@?ED|^X?+49%Sed{w~+B+X~YmvPN zk7J_WzR{t{%K2~I3oPM`jGXBMdg{noy>sQpPq5QtL9W$5<*eMA-lFO0%hq(3(){Z`0E&?rHNVHUpQ75oO!CLc z{U=4U0LL7fwd58t2DkQZI!@cQwu#LE{HEbS{VGXwRRzOh+DoVEUs+Y(IL6F)G#Y5e zJuZv;v0n3=cW2`N0ME_G;x-M{X|^pS<{wYB^qWfuF^BX8_0w!`Uozv7dA)-dX*|*2L((59SR%? z1FQo^f>1#FcLyHuBzg|2S_;rpqIkf!g5lAaH3-67-UL$6S_HTRQI``y00;yj%83mE z4q0dIC4J(d5RvQw#{-$jgyMQv5kyCrQ-U0m-%tPxLHP7gXbl9tyr@PLqKz`KUg45C z-FOp+8df9BsQo=XmG|@GM*TZ~rl1W}3BONKr~^2fn+RnJ{{Yq$u9PX%#+F@3 z{$9aKa?I$cLp?q92sKIjNA&Z%;%Kup`^=c^uj6ii$sZb5v#jWH&zmly)Mi81@SyWY zp;?Nf%cmd7Ky9X7G#g8U!@-jQ*l4}Tmp)v&y~RxC5!t~0UdlKe29T~6ebZ0&vg6)4 z(Cf_6ZB{74!9nzn&`X$jh3u(8X}F4NVJ;nkPhV9lIl7hjgYL^8@k9NA_R_Pd<@|Ns zh5rB^N3-%(FVuZLIra)bp|H~wf%23msM5AOSP)&_JJy(fiO1VX6U+0w;K-e*N#p+a z_zg#q!iRkiATCsh4OL)M zlpP;XsTddzJvnYPJPNqK3rYgzFz^rF7a@ zq8Z^PL3)OHQrB?thVbzY1Z2LlO;p}H%MwA2h;(qzjz=Ea52SS#3&KO!>DFm_vMh{f z9N5nvUj*u)=sgl)&0q(&r>IaFRk8O$2l<>S*YRxwrOQ|IaV&?k08r2cVt&u*pgcob-(q!PkG_?KhcT*px_UboqNN6k6t$>F0EeM|DwHTX!G?x3aOh5| z4Ggjr!!*z*g)4^y2M(ZHh%_?m9xcZwi1Tl&J%}cWt{<%#40lm1Jj_1ooLkFdb?rml zwe6+BYa1#^f7!pf=N^E)*AjXSN~T{K##kb2rtIy(sdAV!+1V4wN$v;JMB)0u^A>YA z6jp~*#@ywKM%#K@)9%I7d&AmGR#5kZ7-3Urr>; zPSa+&nhOm!%1LLP;y7&Vpyqs!LPKMQCaFQi5De+mTf!?7Rl9PNP_~{;aciQ{poFH8D%moA0mhR)Nu zB-91&xi}!7tefer3x*{vEevjRKeZ#4uZEJ;d(~{}+2tBrc+RtZI4?r_%XduDwB05l zZ>8zhT6{>J9hIywC|sOlXnV=&aU}HCg&4;Osw1mv-Meds+*DUWLi_lfbj?9p6X-7# z6kKhj;4w8N-EW|KinitVU$Eq8t(BvgoIuG34_$KTd8D_C8E(PBQ#}4E1s0I$Dve%Z zFb^#?QW~kKSRu%d-(bT8s_*=V^x9Xg{{Y+N_m{OCpxpW~>-#aTay;S`$0LJ+Bgq`O z6$3m1r9sfSk_5!^@Fhkbq{H1%wO~7Gx@M)TN+Htp``t!Tc|$C%rnw=+_d|>EFUv-JM}C--cyi@j<3R@JiQ<~JJdp$sn#!BXBANc8~ZOL5IA z)u*{i%8^|sLL9SH*A?CyaNj5H7mW|fO#m*jy0u-~cER4X+t%MpWL!T$UP$inJuR83 z(^H2}4)mO#L2rZ0EPUINyDiaFUZ2`OX`AOn(;6h&n+rtI%k44eq;o*c=d9Sh#8Spdxa8 zJ&1%n5y9NcODnf0gKmVotkqsrl-)Nh7oiLg=vtN0=Vhd-^UP+La+lzH&wR(7=dT*8 zf=^*YY}}Sf;yv5!%ZH(!<|t3<#@9^ja3Gg*G!*R=$`5NaM@uiX(jRTMhMyQ$iLM-@ zn9T42ApWdq0QH)F2$x4oZDQ)!;d!ImL8e}aZQ_gfcZFjb9Jqdv0mlQ^LQ$f0ES;#U za^YJDGi*o!7`>n;z?AA*r-a6!#WCscI0h)}3cj*v@D@fTqvbxC%e<`|=6~oqmrj37 zcwxicS83pt+VmYBTWx-6K8Nf&x;;5~703H3ojEU6ZapHmL1bD}@kVvxSAkU_aL1yJ z2Uf=@beRV)GYA=AFzgALTAVDkTBz|3mO>6u*l|1$GUD8`=;)(D#chtUXwN9DMdQiq z%=A+z^cKd}@zNa_;CXNed9uY(Fi?h%17)$bQPv$Fn0%@N8~^~ERCkea=&8X%#c6CE zVbMVtFdiM?tBUX>Qz({3)iV}%^2CjpmAlvMZBqNwWHL%pt#%Opgz^y5-W{+P$0 z`|0o*D6Dpty}pj=o`{i*T9k!n#R4ww(k`Yh!9Prw%2~<{E=A%>UtQEYfhw2-PiRcYVk;RW|8Do_WNY(7^$QN~Y&`wCC#@;9X^1CF3Is z3iAX26nf>s4pl>G9QQPi@$%_o0@#)R^gj*wl7bT|M(HDwW{LJK#EUF(*1SH?-~+{+86E1lutZ8OT3 zc(8&*WS%U;iW*s@^1+o|ge1NPNfj2Ss9)*Qx*NEk2`kF=@gbP;@T48rj*y^^fmx*7 zaA!8;=LbnD5lh|06fG~3?kHP|x@lPpam$pw!0@Wv>Ck7L^s{LqCw;&4D?nFKWx*W8 zvel2Af-p{C(HehwYPWVPX_|V=Fq z2CAL(lW7>Q^C+l36y5~`edcDpIdQi(woD;;AdJZ5Cxhp6u{}UyDT&=^ z2e=z*cbjr_wzF>~`*$I<(7mmcoae))f~@B9iucLo7_3_xJjU}r4bF7!y|wIT+rC@v zb38j!Z^Lu2_xK_ja^eA3V+lu(Os3xnp2eyLC#Dh8C7zTj^ywy=uKn zRuhJf9o4n}02H^$`m$SFs^6$TBIGJDClpiNB?ED)YK%N$Ni`A)9OPq)e5C$Rlq!XD z5XpE5P^9r0V5+>s1SW%!jzULXKfIU{K}AvqLj^(VMWo^vj03Kn%1<{dGe7`Cz^UWS zQ;3dUfDtn8?m-=;y~W$!Mi$V%;Fo@gA||+ke%gsiTRmd5=(x5%8^)EHRSDr(kX7PBs{FQw~6!5E0F_XiXizU zdH$}|W=Tf1p(D=^UehFg7R zy>kc|m&nHH+GK45^uC~moW*O0EV}WzD8+hTxa`>Sj;?wS9K-)`ri@w^?<2-XUD$%eoc(mYa@s&xJ4frCEB2p5gp<`H_1Qt+uY!+PhNU zl(HEoZPvSY14t?1hrYv3W13Dz2^&?a@&oO4}G zRonC7dn53#`DjxYY?|g*PjzuU)ys!^;_64-%_8DUrIqfGp3{k}?bYL%%avs7CfvGl z5W3w72bit_8;J!lB}y8CP)Q^yfyj&oV1w&aG_4BPpl*@`B{-?&=x8|O)k{Gd5I!T! zL3ek6E#bqcjw1?%O;f0DxVFX5E)ldRi7}UsgW!qAOq7%9=)pw|jeZ6!j2Db#3ycZr- z$N)N(SED)t;u9!BpeZ~u$pu6#gw6!wLM9o5L{Jq71dd==-PMf@!I;e=nO+7x_YYws z;3~J7Q_QGEef;>zp8gdAXQ$+-13xFn`f3o@r}R__zl}l~^!H;>hW(zMwFoG){wfdi zS;wq>lvZVyer$TtJpOs4uCY>^#-%e7>I_Rq_OP!{c_=)JH7oEL86b~L zDNaQU^!H`ePzF?F8m9fMpWAo%jT0=p%$V%2<1RnS9}`ORM@N=?*>x7Ac@ER?^-y`E z&@9DKC=aw9&+wNY#{f07i|TC#;lU4CPM~w^q}p?}QBLJYy_*w8t(I}G#+Fiat2%yP zU(a?Ivl@f9?L-w`EgwsbUOf~PvV+3|QVck*91p7x%|baAyoe_5&e3`u_SI25r#$-v zO4s)B{{VaZ2ASk=q25Et_bn^I8scu0b2!i;l?Vt0E9vmYqjVrL(0wqf`cWuouvD(c zm^f$vF%Cb|>q-*E6NH^_Iy*dq)mHeKVm?L2nB|e5IulgGsCa z<6KC#>WlYlbkD{<_VD&ny3&m2>!8#l)-Aafy#EU@pU%xMaAD z&|^}su(m0-AK{fD7rgEmU{6O8Nb|t8L9oWcPY+oM@1<*dEKov*LBNH?Wk~u965JXg z8D-hRm94CP7QuK};rZxM_76mXcz&N6m=3x#1Ms&s_$XQ1_sJ z-f;FGi)nV9E?fO%OC`)+{gehl_#8<1Edjd040=8=*kvGK26w2V~3H8yUSzv69xI?;yU1qH{c|OU zM+0yqu1n34?QaFCv!Jy{XyF?}a0ogO02YB{Pz^*bov5G~*j@vkKfB4rdMRa->LmVL zzU%fJjU0W};#2?#R8>roQ`cPPk20dDDQs2C&}#2v7&RU51?we@^5bpj$nD|g29;?mK`+yE7CTnd z6lTvAfz6G}f|$#CNT={&ADK1Ou{A9Yp4K|6(NA=7e@?+mHxHHC3I70r?Az_jcEQ}g z-8Y^cxp2j<77+8Wg&7t!ry87gBi)T(Pt-cU%k2ECPUN@E;phs!^KW@w#q^qN+J4)z zK-Su577$tTw-OpBB1u8TJEfzlgTHaAMX0WXNpn3^mkqufaNQJ<2D-by)TA@&7ngU_ z5~f?riEbXMNFmcX!~l9KnKvwDR%4W>an^0O(&{bJbjRM@wT>Tk81bde3tB=q0s3U) z)j>wD0?St1rA?WZ=99N&zActQV6*_YnT$g656ciIlmXL_;40YLZx@l%wWo8%;Szsi zq*~cEEp6Jvg34RZE0YCujs@8DAgg$3mPY8>Q0}48md_{UA$XNfUTTX*o6A&1Y_xID z1YANB)^ormP72~b1x2i}X~4D0xrM-Rz%B_>gk=LY4Lx+*z!`yoz^;9{AInlp;b>{X zw%<_;k4rcPwa2V9A}?QUQ$@7Ot&TBirvu2)3Sr0IDsUmyve5FgO&{vpSCNm=U!n}Y z;Qp6oU8j%Y_A{f)ZT|o?pF=vHEigQ%E&)0*QHES=e+^Gb8)@b}Tj`h#3vqG|P*r*W zM8=A?TCERO`d}muR`illalr>i6rz^$=-a6G29FtVNW+OZlilSYWAcD_E)>*7iZO`w zZ=n|uTGt>dQ9|UX@(Cb&C^{h42zs~B8b3fDQH#E8F$#l+sL9d}Cx|#lqqJk;j1VXRFieg|-%8{wxc7@=(|HGU0!xDP7UWbQEeO0$ z0*4Sdsx6MjCWFj7#Eg0g$Pk$m$2t{c<9iI6&eA}SU}9n(-d4C^kQr^D zj^XV9NXFfm-xIhs@W~UUj~u^5@LcOk)Tv~3Va+3PRw}QoZ@u$7Qjnw;@LM#GnZ@(H zqoDVqZ#Sr(s#khXDhobgapaOjPgLEymaPnsUfxSA55UI2*cd@y_d$dZxu`uAT&F22 zH-Vbwrgs%4@F`7yL7ct!I&AXX99(T~C3KRN2M8y5S$Y6@4(m0ZaoZDa%pxKHo9h;r&&#{Zk^4lIK!cBEwyBy%r*UH;X&{qY2}J0 zEY~xZMHnw|M7I}mR9Gd8dlKGJrPI$Zaa6%v(n)JVB7jZLap{r>+eq8K?PkkzesX*340ag8av1z39X&Rx* zuge+AG4!%sYvjJ&w+)+6)iq1kG%K4KhQf{q@)gUU3bARRvu&DV%B)8j%1_i- zdt=I7Gf=o?w%XYDI`!Fb9Jcyx{=GVs0j_b5<*W|C_mE>&!o+^WYwI@I<<@`cTH>bK zIa9AcS<|~)@Tcb%u-LpEqiwbyxLWA|@@E0yTlHQd)olqY~@^Ra{p( zLpKW8ZaZFQ4-f<19I3^4mBAMzN;D#dIFcKPFeuT?&=4Xh3Q!&!;*R7J*VQJRLY29VxDC_RVMpwfsWdr+rg9^bN+i0}z` z10m~Qa#RDzPVs>!hFNuWQ4mb_a}^yJ@LCN4ppH|BMNIgC#}vYpP=^fg!ye4eN3x&* z7KA_sRR|E2C<*{`0T_Tq3IU-j;se$&?djuCS|KBt;9Q`PgC3k(D2qU|koB%V5yGGb zrvuLQPue*8u>z2;g)}qt>94+@7c=@Q1kbnA-B5eODz28^`gUK9=_jQ6VoId2c}fi86g&;KXj9&Tf_RoqsXgMAHif? zx1-o{^(`Y?^EnLwBej0o8D=Aiqxt*D12#N zXz23Kn=Yc$k0IK9I%++Kp;?Nf%h!iuP#IF1GE#tr1{4VvmV@Vku*#IDP*h3Q zme1TR!hVVAJW1_ZJ1UNyi2f2Bd@?e}?cqsUzLMH1(;z}I&{wlBQOr`+rUx8Iw&;uZ zdw!{%!-pmqbmKvoHSw$>mhvAQVCFw5p_WHFFvM}UfSDM9d(F)-LMd?DnRH^REBb!( zviXiY6;4B-(8sW`MX>P`Kur3$c2dx`Fy0HpQGmlff!Fb=!nT$uY%{{?>dVuOE5jkU zb^$Sw)_Olbs;YS6!jeXEt_M6Z>&LE<)eS^wu#ayFl_d=#614_5=)IWXKop?!pbz+* zC$|qs3i6fE#k+0J-_*x9ytDL zONy_c^8RA^ar?!1TaF&Sk5^4qL&{{s*u)BOTu`sS3X~Zj+>rVVleH=i90KD>K&(TU z5O(%ZW7E(Y1uGpOz|x9;KeB+xvvwVvVjJ7y)sfP++A@k*#xP3sKcZe-7m5nD+QO$p zM-@p+ZbgvBCf zaqHngbU0bS9dT^efy!iOu#|SK?f?7E`s=#ccanhqiY(_)`qpMYg*6|pazD3 z2T-oX4|^4}V7fdxaYRll!=`}girI^r1}@DrDbkssr5$81h6yD3BYU{02cr}@8Y3zt zh^F&RnfiVAJDA0?yW`A9?(b9$z@J?!$&yVtwY92xPG+{yU=3lF^2&KZEeOW*CqOQC zJ++I5rxrF=+(+AeKH4wY+r$q7SnjASE!#zTw8D2T^+k(At0oYsUp&%~FH#y`t7xl7vR$shWY0 zKs+c=u|%`B7KH6V_a;Vt+a!<1cM}^yEnw*9bBOF(y3{MwbToFnM)y>_q0?h)TENz^ zjc5&PT8(HK00syvM-4JN5w}G_e0p*LflLqNvxt5{wNa9263WN^vQjP7=jwj@J4>4OeomX~Y#wI3XMce}KVVAW32I@}SJ+M5#OU(-f6T6?bydEaT!jV)Gk_=oz!gvl z!nRU}p;1dQ;vF;;0j_pRibgyDAfUlI>QsIxq(56}?F0bO4gjDek_%LE9N1KCAzaBF zt%CqRb_ZSrE&(}_+TAO=aQyj^*r+DMn&--nkt6hiRlhdVAjz>?k= z;hj{GRAEdA2ITV-McdIo#<^|n=x7ny*~#Ng&IxGxiCA9CR&?`slORnAN-)ncCvx4T z@jr`q)9Kc8KD)dxwcSQtM)p>{?+t8GG`qpJfs5hW!qQwr8_cBU;z`c4%HwRoJI+$# zWm~y*p&iT|uH`Pba^h5^js>T@sBo%hJ?B4DdXim5Q@izW`xraK5p)kKpdWPfLyCMW zJ;`C(b9FZ>1=dz?)g0f`Xc;PnTtQL?O4SZV4-i2m@Ga&KQVAd_031#Vx$xxddKYG&qP)mJOuyll6xt`~hP-cBRbqq&8b7PGSA88K{;KbeQI;d{XF&Yz07tc_}X zONuV243}t%LzOXHiQ-oTLaU8Rwnrh8P|~dROTA`^|0Rd^AnHpiMLva7{GY-lAKjX*w>M14Nhl&bxDX_WuAc z(=^Loj>w4>8qmI-_>YrxAcRjr%gjA;pB#PCb6Z=p8!CdV!3w5EeXkIJ7f zwQC(aU|8M3sBT@sBQ71S`J=imqcI(SQ;IMJrrXmV=eJ1HT_I!EG2I%q+bTaqhK*D4hp)#IthOe_+k0BU zq0NPtPLlob;=Iw?Sx2Q=HAXz8u1uGW7k0TS_5D5MmbSS@t$y?ODtk(EUi0(L4bkEY z?eD4C+q@P&&qNStkTs;>&-i>WPVnBBz0dx&FC5-jeLW>FQ+io770|I=x%$eg@>{J= zN6o2S-Rcv)%Ik~UneOLvzgW$1`D899i6v=WWsI>ZlaP zhYa(}3_B32?<&uJn*q-VlG&9{>mmYQ^{*cK~eveuTMzJcKcMY<{Nab)L za_rBuF+i!uVBYv%2qY=uhoGn`b2KO=7~gjZX(SFfluRjF5=d;3ssYG^803Wggwin@ z6Fewhk3&i*oP`O~L`qO7wF2?u(L@4&4NfVaStNDhifB+;0b^SJWdq}hTvVwBg2amv zF=_*!PrUpE0YuIioVtDg02+hBpmipw{BI5Dstj^mg=8Qke+W@upGtl7DFYX!Mxoz9vidSMj$W zMY9n=~{FJZOLN%zhE?w9#J%b9VqXwmXW8ThUYTX}p zeYny~EOI)2UtiAl7n>u%b`OOs%=&C{=?)AZoceUs#IbY&EF7*+)pri6SBF_*P4$)5 z?gYNkdK|w00J4Thm*=XE51@A>5bok29X##eY>z;Q`wpI(${9gsbl3K)vW6HA6n9~-VkOgi2>n( z7TobIwYr4+-(Sjd?WK~TgIc}?)!f5z3!?+_eYq1#8U+@oUmsN?2olv4xnfEHW~JvFGXI84{#md>=zd%K-r|1HIuo}(+OuiB2eGU69vSDA28W=wIEHQr06FLE1BFUZbf_y4oH<5W zezl9C2bEWAT-s;x%J!I-SeTJglH%?ZXu0x&jW!xp>e8|JtZ)(_K#6$R=@>x%f$0EbbnCbnNx6=D=`>+$J_3A zm%5+RKg4movOJIF4j^4ZQI04JkaT+LtI_-?LalY0=wze4drm`>PcDjh9~+g?zBdOn zj#J1~5KsgmYn;{;Jj=IK(;y7BbH7C~MId`eMQ7nLsIhc5Jj9n`7l+$`N|Y>&Sm_mj zPU}GE!!*MafkG&jC^2+;egg*{JW~vj2L(!$sbYrm_VU|F@U)AG3r{u0>xn>wHH;Cl zxnYPnXIgB0Fq<|N_H2?%;{LRO6y z!q~+IY8GGc+tI|^zXAvY*OBd^B-*CR668M(^$=EBGzE4z^<&W7(3I$^D3Ost3nI5* zI5iiFAyP2}oV~RwSfYzl%Nu4lb=oLX^qPUhkI}bwK;c(f@AS~wqg1|{+|Hhc7}Pko zx(Pr(SxR7ox<|gW)a+Ldk;WJp9IYgFpbbz|6NViN#+9)u$5>+sIa*zZ1O*q00(-<^ zhE%ny17p>>01i>gpaRrHRKRm`pIN0~9_@>qS`?yWKtR)i6i#PlkFJwxT#_E4h`hC} zR}xAE`HJv31KUBdQ>J6fZH2T7d4t@b_i=ebc>y36fN{c?XpUS=Zjvb)43BB&3xcm8 zBjo^bM8PSlvMh^F4TNJ<)U7QdbFXe~E!wEJGerS!Fe=jLxvG0Hr=g+?Zo;S5_wL!V z=jGKtq2Q2Xw2E25;y{!J#7ImP)ZB57W!^QYFNv!3W9-A$Ra(!R_Rxp6!%Ln=3>fRV zI#z6ie3w7>Ija@@?pZtFcgrbxm1YkqkGyeHn=nTN5d zRZCGfl@c&dnctMD8bw!U?G)>ZZ*6dxodpIpp_<;GZZdhD4lFmxxz3$QJ6lExER7 zcx%3$_%BSZ%b|NJtB)K^8kxQ&zJ*u1um1pXgPH#T`crXK$HI{lAO&Z*>bp*EqT}FI zdrJ7A6OI*cc)NCkL9bX|8Q)8)M4#FTUd+U$g5h9RteiZ5}}^n}buB6f)b|#|5RN$@(%j zPpW&-GX;e8RnV}b_L;WNFO8>HIX;Dg_m}?I(>%OyBv*PRyf?WveM(91t)+HAtkQT< zIP^K7FB;bf+1jz`J6d~s9Hj)Oz2b}OZ0(7)ol47Z?R_5JOU+5*I4$iUg4z<_nfEiy z#rF9LJ zjfJdcY3}Z4b6XrSv;r|XxS(JJk^m$g8sM`TTXSkL??0!}m#w zs}}Qry!S29U$x`I6~jA+Th=#zy{Ya!$9*=Xt7?-O?ylxSMkyV%3?0jBTWG*!QM7Wg zfC{D7POQT=?oG~Fzoih};4pvyepwt~lXn3|^?+#=sfj~jb;px#(JSb4xPy&*VX(g`91>@6&K%`VYc0@nY zR)i@{YS0mZM7F$AxGpBM>V98o%;wAaW%t*yoU!4DqYfZr%!r4G8K_#sdDeh<9Hcc6 z9I(J)mt{%=DU=%;!X*}p-u%sq9p`!!saMEz!IYzeV`>PLNp|iu4{;K zcPp_0$VXtq1f^m=ke5Q%A`A{Ahp38MDVK8i_oHgjv+)~ zL0?XaA_O02+wG`8lie=|9$-jLj`nJlqT~|n97-HZkEzZCd#C_TaNu)HDgaFHncb$B zXuCiZLb*@?2Xdf7KGXT?5HH$SpcDNRNHo3~9QX3E&mqJR){>sQ1{4Zt>QP^B56xeE zJ}PJB^!U^vs)0A?Kck|d4OAhzgfr7RgfrWp^wa>N)A*<{{{U*wh{mEVv-4xtiw-|d zrCNQ*r&|Rcp6WXbBalX+{4AY~{{S0keUiOL$z*7SrL7KKY19_xr>AdbI8!D{R4(-81{c-hLDv zP0(Ko+#HGaEeuEDF&7U^syBe7b5g?T{eeA#s%9NWe16%V_pknJ{14knERMIA)pgY9 zFLp2>sVNFl}5B6f3CbYPq81X?+beb%k z8ZY>LusC-LaVgA4WhE+gH0$uz@zk*h9>d$ms)ZV5^l9P^t;ZEgX11ZY2`~UZo{p+UMi5)<9{&KggC!^x%Z@(}hgBm?CWA;R z`P1@JFyuUFP5%Ii!Rt@@O{pAB{IP#G9@5EhASPq_cyv+TdcC1%A&xzn{!A%$io!V# ze(H}Bw~+ISzn>024FDtf93k<~c=fFh;2u<(4^`l%{2oixF>IWB!EHd#n51 zn80QG%v?w4psMctwzd`O*YfRkdr`_--jvq8mn~;0m6A32A$vgOdLi#P9DlQ1`No*+ zI2UcI@f8eqEyK7mz%McP8qLCU6bD!|_5gvT^zwiQZXA1P@Rr6KhY0Kk2+7MAW$Io9 zDp8gwG0F=Us74?s4g}%D2UMdhP}pDIUs$+kj48^p$f_|Iochgbw7wI!qJ!{zOA;#~ zf(~RwTmt=x+g2ov$^!VUTo3>gEW+W%aVH8i+*+VHMP>TfCI_$9F&WhBEK%4v#|s#N zt;h^X$0MF7pwyxrAy5l}9C8B!y(qN@rXqbw2Ay~l52ug}GQfKR3X<-!Pq_Ms0do)q zUQnaDT0tEJ2tX?_i3UR>TJI` zNhpmzw54jI$|Kwpw*(-J(=b3F97X|qXf;82N9%ir^7G8-nBFgu!HsJ|U|jOhorO(S z#HPhsisRfHcGQN=tJ?_%J`^t(k{2olq#Wp-pwjTkis5otCGI8KM(EPqKXcRd9Y$2b zMzT9*7Mw>fJEoTu$GRzH2er`^T+_{ADMl;OJKbFFyQm#;X`a4QFChfIqC0QhBSj>$lHIW!K**C}3`-mP&f?LuPE@Ekx26CMkX z40zR3W*IUSdvVS?%B^uy;)9e0Q-)-ZMXH-KT$NE1p5vBikdogOgbpLT(m-4=HCuZO zjr?7ts^aBTE(3-@Vo<5as+qG>kkuv1fCnmr?#!oHr52(?aPU;P{-#7BMc}Av2`Ny5 z?%8>+?MsTKtB~eb#(y51YMYs(2C3FcyS?R4y56>S`11pD5P}qa+lmk?J=`L5wGOde zWDA+faSiR_53C@p^=3-4!}9NBzjxouHQAOO7fvj7^pTMqmr}TM$762u^119B(mOUV z;fWP}n@;1a?xS{xNcB^0RaT>)PeIpOeT|lzXBCE@X$6g)y9b4D13V@$V6Jc=Iqq>7 zxRU2_IM+rgMNujv)4~w96$@!b3t+UfnZ5kUz#vTUOa>x|-C`*rEhPattCI!H0uv72 zG_4BIt4h+fEpJk>(lmP;jYm_|u41#Yv9*lJJ;luK3}jKTU`mxF;MRgl08L6af+CBP zq;vXvHoTH9ol}3?c|W6F>}|2BTk9IN{ASyK?kjnW@LV-m`8HP5xnsHF(Cp`8Ufr;U z-`q6^+^};ynukq3pH1C)T`SSk(LagB)|ltewsco6hg7Rb_f@{-oJw9TE;&(}bfuxf zG72^)a|X_*GBG^>B~CvP`-WPTsCCKi`eqxb#MbH8SZS!4@Q&vNz0MP0;hl+qup7OXC)>(Sn1c7_ja0fjkU%0rF(G|t(BdePoC!T;%A8A zf*4v0-z1JX4msgXO`AtiZSbsQdL6a)-qrjjY`^uM?uyS{^KWt5y5C39qLMfN04)+k zvRGNmi=nfFm>oj(qrO0jJP7BtB^8g;p?x}cBV=O>P{%E_+t9i;x7ycsCkyW_$GA7% z;?#Fu;=XpFtLl=u(!E?Df)@V5#>y8LHrm40G-OeX018x+O>NcGqUROKaXr*aVq2He zuQ!WiU6B$L1Inu5jsvtP;ZlWk7DsN=%2cNVl9j|91_&w+qJuN69w`zt=BQr~IK=F9nI_t&wUy31ECJfIeyI5o;s zCBaV~n#GCdxEN@(Z!haTpyU(~P$p#tIAv0dJO-T$oH&OlqfF_0w=ZZ1)Qw`xdl=V zcoK&Z)kyaU_YD)UygQc%3_;0HLF0%CNL0|vJbdjaIY>~ary>p~15v7?sVbqJ(UHsx zOK}N-Ovul+t16(1R&dModU*QtqE^-KJ=v& zOCSUl0F_1Ko5#Vl7_3V8n1tdJBshCI=nF(7d@G0{P>gWF2>6z;X;fYGThDo04=+Jd&3PM)^kPxDxh&V_Ee)Mha*QwloDi?HexUe?7&=4 zAO@uvd^fZ$);3J;%XNB8hFEn;x`Sar%4dk?GdJ@t3ZS z!pP(ksDBG5V?V|yeUiOL$z*S?ksPB{0%*6R+4&ZVWUX@>C2xJlg5#A(iHd9EblyvL zV@K9pVTXhVwEC%CQNNZi#eGGd)~;*m*Fz(_N(X?6!}=;vG~2Y@%TLnI5Brzukk|y-K}{OV(_++*`xTDpa$q2x(BO>C>JRvI9hC zQfd09o}iw=RlGAf+MFgnY}_?&zYP2hB(gfbUH-Xs7vz5^>h$>1X7N6oZy``%{FE_$ zMavMvz!>Z^Y504pJ>|+j30&^hcdZ8!*B^OGRCzvo-i0zE_N^W>IOe{#o~$vWLxpKK zG}!po06KWorE)i<8dy`&Go^GT6%B>~o_?9q*LFayGBLy8Fi*0VVPkN^Qtn0jbO@K0?h%P`i2QPF|*QZdkDK_n0gdU2&;LG5lz7ntO| zB`PI_Sjlf4VbkW7ptuN|!V*LN{A>i&7|w;7ViwJWc=r zy%lvgoivBLeKPh`41vH9JI?{34?%w_`|45bE{(Z7koj%5WGH2tHX#ej9O6y~yMtJ` zbQ(Mi(HkxIhudui&4hD@%y3>}Kq>uJa`5S*pthIB#8*iuu(El<^hjqCWC%i#aKoxNmD?-IYBHp&G8y@gI$Yj2>V-%+|%*=8%GEmd% z2CKP@TKZ;Y)U@r%bT4W-pI)9Q>&-f zoo1rib0Y*gM{m#qDreP(MB!5c)e(GF!#+@QMJPLP1e~Z2l?BXz1OQ=zfZV;*D9chW zJ&e|k5H!i-G}xLyudRDqO;z&|>Uy0G^_s5KdHUuxf)Bpm0#RVxyxKpcNsjSk+k# zjID7;Pxnk_ZYn`}TtYRYt2#-_iyWAjZYaa7f*ZyhIZ9%saYVx|Ql_cmMov0_7hq!1 z%6f@F5!sHMdT4PXIn`hr4gAo_9Fa=s<&H+l9E>H7jqD-JdmP+Dn&zzzC$KS`mOD#_ zN%Mm42$>eErZ)NQU~8_eZUwB95CWNEbZ?8ByNPN7cvgt&9VpdKrLJcrsd79|dLE?n zes?uA{U#WcVC%b?CU7|C94v%;=)3jF7R#B}yA|8}1n10c%XWE2qAeJQxn~?O;JA){ zSIwUlUb-GErI}ynFG~y(>i+J(`+(mc0Mt(P~gzZveLj?BV}uOQ~;nAQC?&H zM(-VJ=~Lv-%{s-Xs+*;I4+Os%9z$FCVY#pDtp_!fywh709wTFUdItcY7P+Gki|wyX z%k?hw6M3Iq^4B(c>k4x3Z9JUa5Ee-z8a_vyIOZ|pb2Yl=5b zS=|5(Ie@^qaCgR*hIIsQaSO^+s7zII2CQisf}VRbQlXfb18>a;RJhHB|HL zIoh7`eFbN^SLk40{yHE0P2&$;QFkuW4gTV;bs*@Njsq^97<5$>GSKXQDr_LO*qSu4 zv#z0tekh?o|VWrSRLt(D5Qx@7q~bu{1jU z)%Df3pQUM2-d^6^MQ3Ge74|Bb?&rlIO1}S2%}df#y?(HUUzr zG3M&?4rBqY@fuLHf)Jt*N)g0zt_hK-me8o}M5@nl-bVzscSGwUmKk9WlNsXi7+KBY zXDDjqi>uq4tI_cuy=*hLpgTG`+0LfzA7I701do4DxFoQ;i|pAS%aJDB(#aPyX!8da`m>-uu(yhlUY~bc$Rz5)KdBDZPnqFX|4_!A5R>IY0 z+SsZ-TJ6n8Q-3apqiPz9oZ#0pO4F{d2zd$`<7o_myxAh>Rwy^!P4yz!*tv233?(k4 z->{d%F#-c0pfSC zaVbJpZLa*%>P?3Bhf5rF8i*#paXI#3L_?ha03$Sn4b%M0-t#by_2u@1vUJH19$;Xx zk6+ZxPB;XbCjp8rSNybgPF{q)?8#nJYM%}JucgKd)m9SkGjuIIyqjCfA1-uvw0AD` z>rGF5>Hy-7e7lbI+GE}S0P1QF8Xhbca@-eR#q9&vS%rTurn4WsoyX~ur>ISD=jWOB zcVoxYbX`M4mNNzR&%E0@3wD;FX7U{SORXl#!qKIL2P?zGjNt6W56xds7MyocQ_uy@ zd4y&0N)ok`gM@e*+|o*&Cx` zYL3!(R)>9~UZaa!bsF2=FccRWedI;jFz*byml}c%7ZCVPQHx!OAP^~9 zpp8&R;l1wVU@vhPHLWD(2`CqZEd-`CLbmA>5zjl@0K^f@9OOcK!9yA`;%ad3AHsRO z0J)k?upz|UI1Etpt^n{YU|!mv0u`%q=@UCtwv9vh9;%rB9T?1^_6TE=wUeK7B(S)jw0VrZ4l%CO|Mo3;u zrE~b6cN2*VhIFM)wZugB7UmCbDN4|-1vn^AOy~*}L2hY}Z2}FFfL!o@S>F>!VO)_y z@UOj}6EoA-P$pC%o{3NfR0*ntH}3r3kkkR5p2~z2T7MM>`K&(I4%&sbK5TySEJ*YG zm~_)4(=CC+FJJUiY%PdRq5LeL-Twd>q4rAkA0?5$PlX}MGb)5L)AH0H-zU!Q9pgn9 z?swTP*^T}7bRX=8uM>p^bh%zPC0)g$)}S4E_Wo)O(e$0#2Q|EYG_A3zu^XIz>u_+w z7ykf5zB}TlVKcZ7+sCkw|ZJsOcW+4L17?54!2NuXl45)}Prw7kn0?SRPNFtYFr7kxN#6 z^kFmR{{RE2mh*>cR%m_=L{1%EiH%m-vn&Chkl~Vgu}Vjn0*9xjR2dT5Y5lqVi&JI= zvB)LiJ!9UqvRHNq_Vs{h#on)gTAP7P4E;0d(brIIf`$wf`*Wj07AB5AaJT-){!zoC zkn3r_N*x>;O9>|;fM#)A_$3TY0NUWvc~}NNGrPy&f~LaFh+y2VbM383aVabNGQ+Z! z(-RK67{{z`(*O@?;0g|cX?7WH`-?+wIf8h((G`VCWWNk**yMC9WO`-gCEl4X)Uk@q z@QI@daKPXm9!iHhiXhIgUQ$;bP7b~|7NxoOb`}2s;tu;tb*+pHbW&fjujef-E)6Nl z*Csr`gPE&z$>QEeN0Y|la+9wC!Mdnudys?PIeOBqmPblt7VrY%gO6$9_EikV^BLaM zaqJabw+iuMH;j5Q35RE8AfY9=;hN#q(e5={2p>)q2o&G~0E_HF(Tyv}gSLbZ^Ei5! zr=)ynBcaO|^KtDgXXHC*qtR-!Eyh$Plyc&L<+<)31g8J&Y zyiYUcAD0!Vbn6g}nax+YD**}`Fh?dm;xg*(rdXOS!wS5~G&%qbLG!+C+=l;47z>Y1XO+IiqrqIu<7MqS2g@B2)tW`wEPTXj zE>iap+%2H}764P$YgL%87uI(9eWZC>W*}h3g#h5EKYd=3?K+T( zSx|J2E@T+ti6h^dfDHoVhd6Cq`@uyICCp73W?pxN@hh@QiSNs z4}L^{)|&9hne4M8hL@-G^i@`$;i8Qj{#G4N+Q&VQ2Q-_j0#{+~uE4f&ATmpigZVBUcvTeD=|cE8g!jzLMwW0aL(!D1ayw6K+Uo zN%1bUP>@17N6LT?n1RqblC`t9#K#!n4FI_VKrSvPxX=M5TzOj7pu~~|42DdpHDMy4YnC6nyS?OWYYTzpd^YWI0OaQqMgUfM%ZcHUiv8H!bcQKr{+~}l zS8Hu=(_5kw;z&vymLr}R1j?zok}}=yuCk+!HjoMg#}&hYMO+rLvzjPjc)&CP`8`yn zh&(uf`XjV~_JycLU7?}l485oS0I4(!HWx<8oBj&Ga%`_&B@~>|a22N0tPZY@UxZA> z%W_v`(bd!sMOlW^T96@d)x}-wU&%q@Hy*%WR;2=baLa~R(5(Z;z&`#sg2^)?0DhC2 z#Oo?T;5#TeAb6C|1T@@kJ4jW{m+Bs$Bx}o%MR^>c9;Kvn5?A1|b!1NOR+jQ-OECcg zM#e8I8V(I;L5dtn#G0l}$<>+Vamm&~Lw2e%!EkbP;iX0QBn*o%^2(H|yprZP)Bm_(0jopv8@njK45bqvS8(P%F^DYW~rn=I?IOcna z;RRgiQQFxh?ovSS6ViYoD~?{;%`A>x+{)ijz?39~DhW;iFI`iO3|9!%!B)7p^iE5H zg+wSZ!jQ_xTWwAos9PMbWLGR4u4{;Ih=2}g$tm6g<;`fRCaCi+jm0HL-VQzGT# zs?cNRGitY1kXyLRa|034kPF&MhhmK!&IFo9=v8)6jB#kDcXr^@A-uIMbkew5xuKE7 zfTZsOy(o!li<63dhaXjb3Troq({WPnz0J>O3vKMv_n?MpKkVKxV;TShJWXc1a|{vA z)L*Pu3fe%T)%6MPppDLsIOLut7RMVvAjkm)&M$T&JgSa4He^?3U!nIv=MEn^tgwOsc-&Op!_&VvkOy9`eR$fhaMNvS~4J~k_m z1OOwBYEy!h#+xD19HO@Ij8VlC<(cxbC}D+;l1U|z;jzUOYz5LuE64$$5=j6IXezuG zW2n`nm09R7miq&1ejB!feBbk0*|!_!_9ijEWVrfy}eXKQYZ_DKtMs_aUn<; zqJ%vZQX+_@usECoa-eeHryNmnLzNm0L0oV(gY}exNmW$`u@rjf(t{|VF15#*JFn(0 z9R#=)7-Afl41uXaIPDc#Y4cp%M{915l6P{kK}5%y&m$%<1w8_aRl#O7ExAjDX3KL& z+q!<88z12^=HLKjo@R%mu?N9p`;2MFC3i`y;{>`{)Ul?DX+?2deHDF(n8efF2zgfw zIa}Sy2^^yn(kl4dxh$gn2dL5cSIoNleosNGoF3I`d_UfCQT3C}6K3j~{e-%dGwAkK z7WW3yTist=(zNJ=@kJExjyUJL`k5Tar)lPFWH`A2RRUa2+u@pgRIdL3VV9k9erM(F zOf+2L%$YXXYi;dQ za@@$_X?lK{lFt1i#5{sXkVzvNiN><(5|y>bRH}K7X06=QC3(u+IFqd^+s6cxZFRJc zPlo%+4In$|w$GTG)=(5ruIu_n(9>Mzk&EnfJ55&jvBxC5V}Pt_*p4YmU3hyA3YRao z(~K_M-^A2cV~!%o{L;14P;Fl9nw_b8Q1^wiT&Fso zDoT-zQuMz_-6$$8=o)v1(S&Bp zM2kNp%#vKl;t!2HkDfxV7?~z> z+zOmfl{G}GTuc*9%Cg1!K<+zYH0gjUJV;ANND{XWD3vGOCHVdu>qTkGsLSFJ4&{{V7$)9$?D_j?C<{HWP}VnrqAlN#%WuwpaM<#zDA zwzJ&uh5+dqw)L4G9-p}_*y2MPT*>)B<~+qI{0n!LbnoK**PmnBTP|It*2j;P&+%!E zsk%%1FDWlx_H$0RqiVVisdc04I&J=ysoBWr?=<~F)@w`4cZKfD1ngvuTo9rj1uGQe zC`raisUw4S)yp>>tFdvovrSQvx#Q*qju<9+Avl6MPztXihPDi|MAa>DDiPU&6y<=+ zr;SX?n%*=>8_e+MCwQD%s;E!Ct15YjWWb(OavZ{pI+Pqhs8;h1hf9|It#>zt0RI4J zL09sHMs*^qjslG<)EL`y$0&qcUfb9-!($Xu$PhkLiwVer^be82G9B6t(;_*jVG2}@ z+{-1ykBaAZch;mj0LFN3bhH$FYt z)zmXu84A|$l71;rNAC-As)3Nt1M#8(6P!sQaOPeegWXERUu3OeWjO!yT@C@~;o zLWH1|@uG8Ngyy|Ur-l?LEhsd~)O+#$H++9R61SN^*WQnboA`0`>BgZAR3W;AGt&c5 zhI)E>>JZO%6#!?ayQ`w0MK+(sTmJx>&xp#REVJ`t){6>1Pklb))9w%aw00IpAgx3A zS%10yAxp5YQSweRW>L&@6=^tLfAEg%qe1x0<)UU-KHfR0pzxHSCq@Gmh z`i`fUzphus5w5ZVqK|@!S$=C3p^Xx9J1%105d94@qUGepF)LC|( zXPzu=u7WXWe*QHaUH%7CS7E}nMLxmtq%LWef10Um(%3Sbr3#EbIP_A5Bo{y+U5b6Q z^uTqHQ-(P7eqPE+74Pc++}r^DUx&9k8@onXFhRKWV5c6A$|#F)b9hl<+E}_c{leIP zRE7TYzBOAq+HaJH8Gd$lA7urRpxDgxFQ>MP$g$*xEy;zws89B;>-`a*vXPwqZ+1On zHH^Hj87Jm6rZ`ZX>DQcvq#x+Z;Y}AqZK=~di&sM5dajVfv%Z=I2(ih^sg5}1Ow?X# zrlzf2#^Ov%tZu8lQO=g?;z^`;=}7lHIQE=-X(f=XA>#lzs`{~0Nr@;B@q#8R`F(VcA_bx! zkEdNAdO$c81aML9=v4-xHjerWK-6H(JK1)Qlh;Bk6MrrKt|f}*6jZ~f+xaNd7LQ#_ zY8HbEMvAFiKnAG5aG>js;T}X6ct(5+b<4yj;yv`USe|Fh3-IICK-id}RO?hdeRPcS z!R=uuFIsecw48(zAqFxvAyg8p!eR=LXv+(5tkE1M!iM$ z?KwPhEjxQSAZuF;EN3VU1TBrUp z_+yP4BGi^;-Q~n2X*Xm40KN~%6#a#{04DFT!v5y~U>msc;3#I=J>P0<~>6 zojAqz4^}oWxt=H^06Ff>JFRF0m%E8TG2?|A7Qz*DCpOvzoJA!vOhOV#3}N*&DPFR) z)JWMIF<9nEt!%BL!Wbisc!myS=;p=5HU4~rXK(hD6MT-Rm@a|4^p)SVTQv^jO7qb$AmZpQCTcxFV_ z7@~lGlDcns2XNMHf2 zNp%^WaUdXId<%BMz(-rn2Y+%M>)Y-})AcW`g=*X1bL+9X-%!+?+gSoW($TH6w|HNY zM#hp#-lw_CRZ8UFQAG6@t}02JlO({Sa~P3QITl*(cA2fXHrgZy z7r!un-JEdDV?uiacL=6a4Y?x)YT=O2-O zf>0SXN}F}hDDOA`Gv;t;055owP*T+(abs7JZS0+D!aWcN+`?QYkdYZOl0kMxFy!j1 z?MZN*Jtf#HSiPi-R> z)%8;G1IKS6x@~6mRWe;j&~pqv8&fQ!M5J;N-J9CR<8&NUmpq(QsXRJVnqDNj#Nzar zE?S3|nQ%bvR6Ur4_EPZ@yF$&=zgT9dQB#Irl7n=>JXioTIOWWLvz8rYDz_#I)DMbI zVZ)_PSoV`e60ALvj1c;Pz$qE_CuJ*>sMTYguZjr4aVwQw7IbY~MccPGjJ5@? zihfQ~)oFSVxRTdqI}Q~k4k51T<8@(*EtdJotG3hWoqs=t$_FTs<(Q6vhDNFUx~*u8 zOO{)5;z<=A=iZkZkBI3fbAcsQ#DkDk$P?FBMhYiF)reVDD9vY36FtR9P~^;n%$#xd zRd*#)yVI##YEpQFwDAs8!3ttJGOA^f&zq~E1ixajZ5AbmkIuC2BBaq9Q8*Vt6kTl=2&JC?VV9DYng&P?NpIG)U@i%ZJR$(~H<*1FcG z1af(`t6D{Owls^F+bcY~(yrKDZ@P7eT;dMxzJc8OZLEQ8qj0#8bC}vJrv@*-?D~B+ zniB9A6uxuGZrP93*!H58mGe(5*B3KOb8~SlmlNDvNcpZVE+%=BX{VZ3K1pVo?k;nj zNpUJoCON#y9b2o#uyKy-dk8154_z?GP}Z(t+z|li%}yLWbpnv8I9y9ZT1%W**02KS zz0Pm}t!sIU-uDmy3ReL`G1A<3_m!SoZNDNmsPnJQ`(if;Xgfn{JHvJ5oy_rbS^;eG zl5RVu5>d*_2vzR;#pHCUho${rPgP5zxKmZ5+V?HXdwF!_f~$O~=2w{;kIg;HbGf$+ zyu8H0rRgxp_gHAUcQ-UvHf?fdah605nniQC0V`ghN?b`LgceKR(%#qEf=MV-V{4Bq z1_R~<;BY8ZA8is-aHcr96}T3hLFhdd22r4USX3wg@WAw~L6Q#)XmB~SlF~CMM+^#f z@Td`NHtl;tg5bC>0HC1*GCtZV43$USw{9WS-oGffd0{z%WQ-Bafe$G5Q`=OLeT7S% zg`a2(rig%lNEA{OwdvBh^zk*jP4na1>}S+JN~ELb-2|lR730;szoxu9s8ZD&!#1c; zxnZ6))ORh%IWI~2w^`<&EodsWn*AgswmY!m{_1XCp>>-(#l^od0AP;pk`4thQayEo z{Np>7i>jyAerEkt=bWpb`Ht4FlwMc#(9y!%l2S=cT)if~B&&p?>Z-BMsnVVNLqtZ! zvMta3Nn%N9C0g4n@)0pVmbv=Rl)m!!pC0Fn`8EDx_A`vy&;sVtP8p%hs69P(&0eF* zuTU_=AZO4@p&ejRplBS!$Z^HEHP{S~Zv^zlpvwUF!pFC|(4@DKLIM>K;lxl2Nr?9y zx&B)_e^zKY6rlu46cyr439JOaAH#iRbtDEb9(i#cjObdV!?`2aw! zC;$MVIUKqNO$LN(XxT3~k(0z;$*tVkVSG+>axz90W22F%_@rx)f(SIV#I4q~S)$#t z^O>67n{T%~<=d?D7ecm=z_Hg`D=a+1+tI1zc9*#WOSK50=3l40=WEdm(+beF98uuZ zE;#S|OR4>5N<7hCwybkVc=nc3%H(>bx2fm6W!JorZoj8oZhs~<_W6~dK*Kfuh{y$( zm|J!t0W8(_%iZGNMGEr$MW2Yr@~C1`PfNzQ^v{kvzS8SoQC03!<3Z{N`0IOTFb zdOLSjXSn9OyLUwC86zDWkw{~fIT+~Wjq#{-bH?D=BYRz&NCa_8$I$aQGK)@+F`8x! z%RzTZ_XX=Ttf`>=H%k|M^UTro@wfm9PZ{9!<>3w|Bb7+Txa=C;zARos9aj8KU2SaV zM*~kIoWKW+0Yb**%My$S5spbg!ZTK;JxS}`XrHmwQo;$BiO!Bii$0>^JCVF55=<>L0?%*w68bA7o8O z$z*TSkD}5XlRY?8A)TJTx2mBA`9Emx(w^US6_JkTef5(Km;7zV`9tGVY18FjF_yG> z`o1~UYhmA63Zu`}`6;$`mMo3j58ZBhd?1yRi(lceA z;s*TvUumhZuqE6nSyRY~>Ld4}zK931lH)nrs`!KE&Hn(szw>9Ku9jJLeMd{nzt=9J z`({I@4^E0h>2}4<`)N_8A?^8H>-bQXeTCZ!_jUaCpV@!JP}$mfo_m}+h{dV<__$*4 z@H&aWx8ELWqd*`%U33`hBV*Imickn5Fdc)cnz#(|r$|G_XR9w=D-~_)0MUud^m=*$ z_R`k??+i>z@K1#_Ly&8Kq$j^BAX%0sjz4g=>S;gq%~sji(|n{jNrRetkGz40V$!1X zV~=n2Qx3@f<^CSmANH>JWskOzybK!dsA@Se{8q-7(_xow-%Tub zHqhHPX`amu&X!XX8tG*&C0z1cRMq;JszoJ+KI5IuAqc|Kf=PT?Dt_tCtYFKdwL5Y( zmW>>$dIw7zPpF-|!DC`M{1gL&eQx&(2^U+1UqLppgnd=A02|`Q5Dc;E>Ze-`kY5t( zC$Yj9d!baE$G{G%u-b+3MCT^}qY{b=Wz#^hDz1UTJ9do0abA>8j4?bYP_ab3Jh+Gc z6LdgN_=B-e`v?F5dh11%@U_x*v=RI_Ot`(pqZDlfE+k^7r2GKtt8+R@Vo0|n9Dz!wIz zlYqm-XF?`)9z8=Wi5}ha7Vk*0j>7iS`GvK-ejU86ltXnCEd(|v7d6bCi~$--5IB)k z%Gz$LGMVY)H^mo5SpH*qt9v%_*I%b;me$-GR4mzbP|+g{ucI7y5y8?%3C;D%#j=bbB0)~>PfFbqLIlY2COW20#cNU{AA5vokCoDm+`^Phr z9-3D|+K!2I>n&2k=ECWto5G!lUd+5m z+&S4SXZ4y$swWvi(n^fWa^1@^;JBP*HJ7=^0--QcNL3vf;nhlnOxP|6T1i~+3DJTK zK{V#Op-Dn&`aEe@x&>__9ZPU=shavO|2kf7}hmg$lMVK!vmPjPC4UOQi_JS z#Uwkz#|)9(h#k?`a_p#C6^Q+3F=rrmH#b;L={bYhOtR@TL6X}mk^m|)YNL~|l)w^| zWLc^bCK`UD%33+j1R*6+4r!VwPgPi*I*nH6Mog@=r?domga}C`5Mz=NjXImyYmL^2 zk+>hoTg{J81>(F)4_zKQ=3C>ohTE-8@-+9xcsCwU6F%Qv2E=z67`nMHP`DA$cDa_IaDteQF%LJr&qM0F@NVz~k#C76V06NpQt7xu*OSYxF ziZd~$Y4d~jQi5r4vZ>vXhuzu)MYTK=A0ABj;Z)thoUKy$(p!qpax?cRd69mk0nk!6 zkQ8-+*IDO^FkW8{JII}!ZWlI$h5PtOOiK98z~G#rC@;6RuWrl#04}zE zp%Z3T4LDE7hg&(4@dyta&n=VxtehB8aWT};y@mCTkhIDJODcuLyB>@AoKvT~5D zdQ-31Sfb;H+uQso0Y^oILR8IvXW?mRco)o!x>J4HDhl zVCm-|I#-0jp{)}x}!0M z12dRMd}_mDR2(2cfB?{VHCvGcqGWOPQz6zv)C3>{0z*)uWSwY;cvGU-l?UPg^K5$} zM|b^2WT0d}biPzQb)Q%AyMD8o&cCEvyhZG18vs=~d#DR~2_W*$iXv{kda-bH2V^CVx5aG-S8ITn?gkG9~T_TOqZr5pZ zJ%j>efRKO^;VV&b!9b_Du&NHFFAl4qtqeTPJmJVfA>{-wNN7a|Dx~Mrc!BY9IJGid zQ!&8hm|9v;N>D{4XFt+zM*yRaDaMtdTG}MI?=TW^{ai5}AR?|kG^|NvNA6w7RJR3D z(ZN@NG#N|4W4I1$u*tDO}E-di{x>56z<*%|=|TD-0p8dTgR#TL|%u${{{70WV|ZY-K~ zXt`bHR?hP?mre7tY}~h7Eo>IPZ0ub=3th(&1ddJBp+v~E-N^$=bI8!kg!w|LtsXVI z-wvif7w^Sm+OlU+>-4(+0K?|i-;1*KwMl<&IZLlS#6xp_9yg|)Zw|Y)Cb07pWM0bF z+iu;g^E6g$c>ptT=`$AUJAUdWxEUKeIS1>hN=uqciE%=cx4nDTXwRtRny5ux(>UMK zH-zD{??vMmzPNOdy~nk64I;|c(ayAk>Kf)V!yOa zE(4(oM%EGSrQ+xn-KI!ypIE$dhImW>6Wn5u7cg90PqLNCi7T_&L9Sct={{?Rg-?`- z@jyq)8=Wr{irnP$Vi_LejIVL)dyXEr94J&*(j94IeSY@GrrF-#Oh+>!v9-9omBae6 z+p9sP&LR9I)+HSSV`K`=hX5w4gqW=Jzgr*-Q^|(Iockdr6wrDs=-DiGj z7t^%i+%`G`wTZk!xHOTCKjoypWUa@pOWjjSe}{&wCv?7fI2NI!&JO%Wbj^YvsG6~9F*nO()Lu! z<;g9-h8l~n_@x*x`BP^GSKN=!W_s!n%7isUhW#+013f)D>JZQ9s6#kZ4$(!Y@lb!7 z&yVD&i!A)u{p472`e9DC9-V9t8By3-5S>H#X+OFCF+=Q?>OM;&W{~9>>FMjJLq0t| z&WeN^}XxbN&0$DgZ*tD>7{ zX=2FT$MBawFoAW z%L12?71TmqA5H?Ro+m$5^9Q}1{{VY`=Fk3B31!*!9WO5b09?9@>M?-q61eNLOC#2!oNz@*I1gXQT@^sQLZ3BuXezYr}uPRNW z1?P_+OlnXg#g^aI?|^(3GI(x6C7B?IgZf<37MC?T7YwLyN$f6#o07&W0#9q@{ma@P zd3_v2;2UoXrUBW8x<|SuoYeqYJ`<)a(H#_?Gjteq%U9ZKPqdJN`+22>&L1nC6r8-l zLZQrd)oD@Fq_>yF$6BKEO`>?{!f`m`(fMjLu{}~%XbWS+C>=w$rh^uSivo4@XVvaB zUeGkCLARS$&OVO+0Qg;1O<;MCFt5X1W!3nPZ#Actfj{}zyTktgH=-Q1 zHSXH}*Zu>V83+T_nf$rdHtKScBsx4GiJX9yjj9Geq`2|#8Zp{vva@8fPK7S5`IS6Y z3^C#lVW->DYl2)$tgxeryr>7-c=Y16V@Bm2!swzPfSG30;oFc@p+s1f2NdOE!@t69)Uk?0_s=B6jY zlzcl8Dl8dvGw3WhV{1^k4?)2(2R>w@)LWVtvDkuN^8@-p8`aQp>7X*GPNgG7jHjEt z?nbvR^NWE44Dw!znIgr37*JU30&6Al4i1g? zuZ*m0V;mG)G1%gT#A1MBp6%5?hCS1ts@5y3!?*JGz5f8Pu7$%wCOBh`Tu>BB%werV z1<5%C=;BAMQ{7RqtGGFr4@`kxkcsT&!j_{Z$oA%#{Uieq+DN%zu%0Q3B_!SjSKHz3 zD4{-BXMxNdJ;t%ooOL4v_@Ux^IN?w{TMe0l|b-P!tcOxg%j;Hd+%5Cp^p)yJAZFKfaGu+qS-ohRl zuLqhNZ>W?IMnnqPXHRj%Vn?Ip{Igj*HEBh3weG2Q>HbH}-FB(3HN0IOooc?JJ=A~$ z(0K~hm8E}bM_%73fAuHmUibAATOTNPouF%{w!PG(2(G=ob6Dh5cZF;8?ZtSgs}`El z&aBxES*DBllauLxRSCU@*wJ*>v{-1GWuhSuiqZ~ncr3Ct&n`VGrmRqed#NL2H1@6c zAr;s^v5x=*Z~)*Cf$Iv3_EVRn=nWn-4+E`0_u!N^g)B<@%?%dQRN4BK)X_BEXz?9F z&U{3W_-|mijjtH^R}d1{H!fs3fmCW>rER^a#msF+=9*G#;*Kfq-|0CIxwJ^tCZ#42#C3&@c=2)k^B2X+4dG482uVBii})Q5Ps*UYIov9-Cp zkTTNJ=I&C|479hJ;lz{P?!woSSJzh2ys1lei^%f1clh@WvY`d3rZEXT3;^^kTZPmzX zl?`b&@wz}iL>DOPt2`#V&Wq{PYmSk_=1C;}n`k>hqIo@|hMWE8tn2eNNrOeQ@U7lP zU_H^i1MI)}=fIU^LxnMl}JhX~_`ss&H}w6?*Ahb``sHB6r$pX&{Ug;)Zyz!KHLdvL0nqcX}QersX@?xrLvb&?R_mRRsKtra+p zSMd>UNX=W!i-&j|3aK=W72HiMGb(M|(r+w^=W61;AAcdl;wwGe(aO~K*H^r)cN+)M zLZN?oZYL0;H=k%)qFER(uYq-uYa#pHs*FCS1yoPkx7aYM)@2ayF0<61Ew<*7vpl-l z9%S!J8(m6k?G{Nqz1a7FIlGqf-&E9F{-xYD!R?kEm!#YUYfme+JBIjd`=-c8UfN33 zTix{fe#-kzG-Dk#xyv;i7fyc0pHofdlZ5M9zOA|SjYn{6*Vj6pt*YHgd3}9wRr1Q> zfvji&klVu6h~tT%GC0~A&>8@s+is)3iSazdJ`n*x?x(w?bY)P1jg6(a<&S93B^&{$ zKunA^dA?AkuE7ZYvBMF@r2|8_Jd^T+K-hb9`RC_EaoTA15XFDBcIDJUOPi_P-_!LQ zojzAL>aT8I8}v40DgCsF&=JyW?|VOQv#%@T7Yx2-D&EL0pW^mpzBfqFzHfQ8xP0N< z?$z}-NoS;5I#_n@fXR97bgMTK*Glh|mpY}}KsOVdNk%mo&?HBKRU~HxV zi~tdW;f`Sl&my%5hbUkDXD>?h4(Mnrfdd?=km;3Ip3e{2c3=f**X)&t3=O65a&#O$ zwN9=67irDs4!TA6*Rh<7;I&YdL@XnS$Yp?2pJj6oHA4eSNe8r*98O~&nQ9Oi04s=A zySk+n0X-QxY7jB3ZU{1SNIZB1JxN3&y~K~xCcvSZ07FJUwQgO1Z9UHD*XZrOv{vP= zX&^a4!CaVh9054`>VpLshFIi~wYYP%lq_SU<{5DaR|ze8 zbrOw0PfZRMPYQ1&z_)E`Z#>2p#HssPnA|!@Z;0&Za561D#vr@dvNfJ1#r3=`#GYPr z$`p5re}ur9Q1I}@i7g{at)wtXq-c<|i=A7XkI{|MhUfB*K`GA+>3CqXWgZo!-jK_6 zCfI9BV~%^78;bJE^dt07F^nLkd>aa+^y>vy#(8yHDB70VWJ=I^OWPAN!{uupLBpn% zx@;`FEUuWVw`drW=j(cnkzi=eK*-gfmDD_3 zXu4Wn(_-Yj&8o`Ngm=xx3;t$}JITu}Ie*mtZh23jk@H*HZVS!VdIu_y*f!psd2b`J zKpQiZ(EPGrGUc_~Ut$OPTp|5Nt4#;FNzMNN%Ad4O?YHDUnU168R`}enZgFibg$|c) z@TyBS4bQ4YGgJir6wu((N4+^qsxtuzbiLp zZsT*`umVUT_jBqRlictV64X(ZPt7Ir?c0xKeXWE2RYUrH zHk#j02OIRwu?6p+zHi;c>v%WDqoUhC=Cs>3npW4f<-@ol&}=LfqpXsd(!E}x;H1*% zqV@|ubIl~UV|q}I=p_aJ04hakt8NX!uD#FJ_3dw1bIQ2ty0)RH1ybTeZmuP3N|TTY zRZ4T8dAHWib=&vvKgOKp*4y@x*FxC&p3YVdhO`5T0~Z`}JwlR(cBzUV?B*91piBXD zWXAWR^M;SpR0Nj06{b$B6GR^raUeGg@a37U2bHqO0T`Z8@&`y-r)iE@H$$82?`Olz z^z=%EGNBCg{FMl2<@EMc0iA15CUpBM0M0c6D75}6-~85pii#|=^P|>Sk#+iKM{()5 z2M@NNVQg{<)IWujv7h4Iu2KhdB{{U$AQARtR_DnWk@wXr4 zH`t`md7VC0^BHQ#_Ie6Mu=EQdTz_Y}j?U51yN}^6xP8Q*;WP<9min1j56zu6!-BIM zt^WXIJ5R=un{D?HC+0go+AA*6!jV(A9?x}2?5UYshRFoEnEG_kl_}T*OipJGpq{T)F2FkQf^h(qIDSEmGNmyAu<#lPQ;8iIjC$6K zV^h#iG^ro_zT6BIgp|+bI4xJ1)zi1(A!SXAxN zQi*Q$qb6uOGfG8Mx%Fj1nrWJLeJHgbbGPdqSA(^C&U@tl0M@JA{{W4+&xhU|Yb)EoKHk2Xjpl`G0uQ{}evz1d zzpJ?MomV`MGv*ceaqBEUJNIUbxbAkAm!-Pt_d8EWn@ZK4HM^jP>!goMSgl<5bI!<( zqr;ub){=-wt924hq_d4{>N#A(>@G?+icUc&q)$Z5O*iTCorO- zpeVHKBTcNCDzOW9ZH7}vhXIJ_rbdW&*I8r57+jY;eR(EX8gru*IL8qwhB$h`$njA^ zg|Sa);S?PNyQpRA#)?IXCCBCbNu}KY5Ap6)MaBq|rwl6lOW|i*Lxs0Z<%Y=006_pR z4}VAjdg{+<&`5XFCvBb6Bzs<1g|1A|!{$yUQ?`O-k#uCB>(-I{jFw(FBz!Q$slfG7 zaz(sI751|&uZPO$B4m&&)#c7Qg9i@2<#h1?DSQ&IqdaK3qn9 z(N(+g*;dM>%q_0!ll2ZBVR@mfzjo`4v9qzVXNzgd_cmLpf9}g1RppVdYiTZBmAAmD zfG+B;>E`U)r6ad)RqEPD_U7;0m(Sed<5;}p{{TepbK$`8Z5|eE3CjSC6kHW)V>!;e zPYxCfH}2kZ`al}xv>&5q2+7{Zo zTGzv6cE=+}LJx-otnJ`qs3youz6zfo>7mjT*rBDzt$J`zRtrk%GR#oYh`*3_^i&^U z?Wk-G&#US7mr+>@OIc)*(cEeheJGK>2_ex$870e@LHW5rKKe4Xa#5`?=`EIrQ+C>t zREyt-?I4yOOWZq^+LpT%V4PyHv~-8832tOy_43tAO!l)RIa7GfYq5P~+fVY3X8lNhJ*|z{o>D>0arNU+WUBEGV;jw$T{!fB zxf&H5QUgx_a_Bu?x`<`dkha>^;^>B2+gw~clnu9rS&HS9Z7D(E7bvAx(nj}3mpjK? z+B=5Y&qJCw+-Xy`GkYXfOvjOV&BNN}00}BaRaMDF!NE1e(U((l-P;qh2`4!!F0+^0 zN!xeXYSBj&aaZ~)-mYRCC%V>u=C>7mG6w}JjW$VchZ!Wee% zk$O2FUAy-l=#`2L8b+Qh?PTRe4Vp;fAd~+9ZTV_&*jcvzpG~1wxoy7uZiWM|0~p4R$Vv>m;^>oQgqh;6?|kk=&cx?kakV((g_7VeFvD$34&IB)!KMzDE~4 z^tsYGNx<%V%0SH%Fr66F7A85vJAhRMQzhj*0y!PBJu0v%@i$!Hc1>0H$BCS z+^^4>(Q_P7n^WSAcKt@=-OF{`N9jtn^m|!M*}t4L{KOO?LiJ*Yg$;ti)SYQoE|@fO zfI0x_C`nQakuVixV^d|QRIv2TuQkgWBZ$0^1}!KMU>oZ=)to9hnz$ns@iTg^&6+5S zq=0~!6m}S=6+C+=aHp1HyLRW@b!9`XX&+AF&P)bQ1G;elaa5{h8ey=-de;e8>UMmu zcPLJP0O|QoWP#CHq_!OEks`MYK<6T!UQkIL(fY%IIQ3O!6r!%tiuy8bi?jus{edD_A?d`VC)7;yZ zb3A3N(DV&cQ!Qhnla=MxcC#=s2h2!n3{6+z+>-cHYoQcNO{l#O!nZlm?2CT1iJsf? zrY|j~i+XwEq>DRC3!6c;{Iru&v~JDN~`n|o(z&R~~PxSPVBW~ICX zoam%STWj%6Mgrnq5M#W%Am(knLF=%cDnqpr6$xxFm( z9m}|`TXm`GIy79~1WmQGv9*H#08oO*Ow%ml4T8r@(`{JTt>KIYMH>LbsXI8F;~6~K z_p`ql&h@BVoj9C!Wt+?C((1&f0@m|2zcJ=^0ZBaoW?V-`RVW@EzUnXEq2sxp!B(PB^bkT4@I6AP*P?0mBkO?_yVhT7(UYHn^NK2p2EWDc6GW zN`xw}JDd*AvrI#^+e7~V?zg}bm;^6XQL6b}r#qW}O1}E`GnKhP0|dvL3;-#LrX&(8 zoO!by8AS&b1Qhn7Vwh2&Xkr$j1ZIVDQ<=xxP?;DBC7?Kmb2R`2^A|h-8knAAml}wO zl56@T?YE8#=f`8?xsyv1$Ttx})6B=o5k}jIUGj&clN3ZI*~bbz4HxXXQhyC`=^<`@ z->_*6iPzGz5cdN1IlzoVoy!p&fTdOVap`7C*oIiKzHP@NNAC7E@bdF1R2-)+>}`w? z7Ka&Ry;WFTi`GSnySoR1d+@^D-Jx*T!kyqwkN`n~ySux)ySuwffWYZG=l0(({aVla z+q>3UbB>|6C>TWnrsUd|izEVo(w+lR(Dgn<{Z4+)TfyOW!-3F6KcE|j8vNm_FEj_g_&S;er7m(~)Hvn5E zFn;rLHp{qfX!#mcYur`4z7cmkqPPfu1Js{gw@sI*L35;DxzAGDe(pc!SNE5Tb15+p4BkHO9KRXhfAR99l|0pu)GL*D2EV%(S*jgb zWRC9~S!g>_*#qqO3w@)>GS8#5bSCgzPw15in~&x%u82r6t-&jN6ikeAwd7gq-s7@a zb(Ty;I7aNu84v`bAylhX{K?*m+@N=8ikmMT23=hGSFWbe@uJS$5`5!U^l&a%(+v90 z{e8Sv+|=&FGh-MSO_%p_+Aw7DPrS&X1%%dC1;lhoSRZv*QjK^Hfn=(;X=~hVlF?H=g=#=i>73BN(a*SNkrPA&1k zQ>C1S%QE(GNiO02_!7uDO=ZfQ^4A#ewVoF+Hu3MjpOI9nMYq2lA1>#tr#gn;cHwAI zd~N8X9#2gpWOm`%T?xraJ6%eX`C`At(itO=@bN~}YP#q=sxTD!$<4 z{_gB_n_HN$K&xLPnb@VDspqZ|FOAUPSy8d5Hv}aagSyakCVR2iJ`ucgmy%~2N2x-B z@I3Iq#33-JgOc@vKbF?;p`!Rz#wQN`7l2F7EJ|4$Q>^$G*{TS&zO3eH{ynod=GB73 zf}H~}_fHYd-hH_#U`ZJptxXG4-W$f4QV8a~wrJfI1Kc5Eg@6zgl$iCMz$hvS{0v&S z$sN`WkEz>xEz=`~pn1BqXPl`lcV*?lj3i3zOIj8 zgcZX-5LT<~RkZs40Cezw~>m50JfNM<5{gQd2v8M z-nKMa!kN)wkAJi3d;y*ra3(nIn!Bl&b!Rz9g6WJL9o%jC*uH>rVZU9R z4nXG+adJvgPI3?Cb2*<9__|WqsKI%L`g3a`=o(W9N@IostH-XFhInccbvwQca*w|(PIGX1=pO>AA(rAc7X zA#N9F(U*b6+75z6SWD;B&t;2Wm#VL?Et}ns7GKcbWVFv|60HDvk~Vvj-X_B&XD1#Y6qa<}1s?LERA_jt`2A<>UNz*@yRSlYzyr$& z$3=11!K{%GF3T!l>*iV6FITE z+=>?{gJka}sN7#I(|M?FlsiaLmBGZ#%i(+ zXLsg+8r>?bj;s(dwY3@mgr5$JZotkx=#$~!!up0c+qYEdC;_OUlH4#psI z4pkgETq8t?fHnk9QW$)2#i)%Oo03{TUHx=c^Rp}Er#G=D+`cPJ&bUcZs?x&5Flv_z zHdP2G#YA7(HkX$A^&aW51^;?{@-1+u=?PZxm}TD;MeB=rL0=NZ!sUS@C8R*EEGvH$ zsz@$`I!n~vkbtwr4{0mjWwkR4i({$1l^PGNW1jTsA~yddK6pcg;m>g9I9hgYtW1bJ z4+d`YgOEHf?mb6o9uPGPeu{tQG&W{=*Vc)^{(iM<9B<}UtSf!veWQ2C?He$yMC3{} z7+Vru0iKzyipLlhI^hq)XEZr|fAnPY`kyFqD7w>$_zr3y^{D{oS7$+34jA04(*9(h z0id^x!ieK={!>bgPCY%+exw)$IrNpvH_lNvfb>@sD6GgOB{e7<_;4I?Rds6QWgoHp zlW40jI03_l{J$+;|3LKba3b)sW60$x7kW^cO>Vw3Z20Xc*PgDvI+h3QkrUZbrZv)` z(Q@ySz++*s2>#NdYEELFYOyw=V*W`fpN44D2RTyEjX3(z^c{1dzP9?%$N7)YJ&*K* zosSK_r|R#N=s@_sg92GZ~=p?l6 zVa4Oi(FJdPJXU#sn^t*O72Vq??)36~g%azhdPG6w^|RR$j|) zLOf$+n3T0k`FikB_e_zhdTh~;BtJr7|27gSlO4UfyZ}{9$WBq)v~n zDcU=yH~Q8&`ylLccDr%m`|h#5o{^uzvV3#-c)HA!_;tbUwTg)nmfapiRzBr(wOm0j z;42uxuU)xppqVwc)MQ13&-m(;;`{7L5_Vz^cMR6I4#$Z4pTa6+h z)0ZJ2X8~kGgerY4f)@4bYPb&3MNylfl!MzCp}K)pLd4crF11idmX6V{)wE}OPFnBj z&))X+W}|4q7(-rs0^fk06W53geJ)B_a?nPWg>c`!yPH5vmOLBj2HE>g*K$UhL}TE=2oT5N|C!sSj(ZkZySrkJbg!#G?=9Fvm`CU+zP;WyRPZig0)>tHPJJBJ z0lLSV1!NC05+4J_gR?O83SWLk<9Co?R1Cly>68tI-CY%u`yaL(-|jWGCf_*O0p$tw z^}VG~SsC=zBCs%bVx1K0MbWxg!t6KJC|aUF&K{2hZ1!AescRzZ*hR|Eyk#As0PRU} z{PIg+Os*oeU{1pYfjhI-jb^%T^);1O!gSJrGm9gCw-ul>i7E7FHMI>jEt9U|M1AW8 z4Hpg!J>)l6d6@y&UOGzHDYha8tkLD*eZ(VxrL*nHyh?O%Blo6+Nj^4~jorxs&PSYUF)Kp8H=fbDq#U|fT zdYff_s?m~~YqLPulEZG+OGb!NGip{9Q*&cmy9ZkYpkBIrEi{G$*PJua4lg#{U(rRj zZJLoZs;3lnBoQdt^|a{evt8uAw<$Mo6282Wk$mLu>>gD5IH!!aN<9ve0?D~}J+f9l zs7I8P5WU;H4%lR6=gg^u`^$xLot-jZ>_`_2r{tnM`+R2r2U$7dEkvT3g`>e2B(5Ax zNj#m`x(g{7?U?si1`rFsitqmyUG4e06n|MhcZw(0`tameu%_71=$`QJDKB6w=`TwW z)u-OMg+atoi7pA|_|CI<<6qlmP|K!BPTJ)c@L#>&m~Np=28E&oMdd#YXZ?w~Bv;y# zX4C)H4&z^~t`20Tb5aAUF8S~l{Z_Ar6Vq~(oB0OW^_`6-=2VtQbVSBJs{eS*cfP}U z<6d9AR`HYG<<<+d2o4NL0B4y8|GVLpc6OkVX1^H!h4|Yf&RHq?X{#E9ZUYuEi$qSK z#k~8JR)*BlPf`6+0|P3He}_=@5=8F@xjvg>xT$g8LXRSZy5iFA4QxmOa@~=Mv0?1V zX}wpAX^+OV8JK*quc-VF9N^D0`4P{`w;w9N;_JPFH^I+w#iCOT(xVy9gAtZ=t7K#j z%8fMt8wdPO3&9*_Y6Bk>;Ps`?C{8Jr7lFA9Zy54bp?BuU@8)7ZLXXGW+&4I}6nb(B z+ERZ7MWmSQug#(qkei)V1B9gGaOTPMsTPdPvdbON>!ZXEllo3tgG&={(-3Oyj>IVE z1k_=Xgi!xL0JUA+(Qd}+_~0r`dPTmc)9=Ym0@K(9B!j9&j42M}Sn2G7LtN9YnXM}t| zB?@0(_29FgH{6tXv3ZF#SNd^!Z56jQ73V2OguK2&k8-R~{sL$U2h^A>-R(&%GbplD za;N{$Pq`R3Sn!z8v#xfl^517b`I8uWZGUxjiqePWhO_eDhNfY*IqW^Wd_u(MO zsb$$=a0qO6#&Q|V1egR>A*!5-VxtO8j*E+Hmpn%4*X}*5gKs%_V^eYAuSD~!aU$hl zzNAtvC@fXf{tYLgF;yxmoS6c9QEv7Zya!Oi@_uGOAD7dk5NBNl$4Y+S2y|>9vD}o zWDv0@qmy1BvEk1)v-P&7O&e>C!%E6B5M6>)|1xQJmQbV9lb!>7rZAj{jZJbb1w(Kb z{bs#l{1YO*OzHIAjojl4W%+XsXIAAp8*JQsFDfj~bz^XS__ShUiWaSBEj<4=YpXU= z`gn_sy1`x!H#A@kP{<%h^anGErLFqh!^t}Hu#_G)w`?98;K2sb~Ap}akw)s3k-!djnhnI-l|TUVhT-#ijt&e>?W zGJ7HIXE&oK$qXIklayg0fky6~*sxwm5YPi1*S}kDauS`d@!{V*5Hp3v*9_g zqK|ek2dfQ{Bs!wHaoq@Vb*^Z)9NVLomQCYopa5Ho7)ypL#%2_ZmiBUjk7kxbm0SmQ zxX^KTFLiF!v&Anp@fOZELXxXGVD=wBRe4bN){VrWKtIM><%|fUdTLoRF7=cG-S77+ zSY6(HqtEtbj}mPPzbB4AP}K`gyCsf3x$aO#9lMSJl41=c>iO8B$BaXMBT{iDRt%E# zP{TEc#@tMRI{*cqjLAhk;|}=v3}`A)KIa{+8;PfM%Vvdb3wX});Yf6UXd5t^rV|Ak zME=rb`fd?m36VAi$>o3WePe-w%8Nfm3fD<;9QS?x%!uJUbTybD*t`DPM+!E@~e+@ zip#11>O#-ohV`&8s+P7bg-xGdECd9H3*4I3`LIoHR3FwCj7*9#*M!d-yIcKDD~$rKxGY=jr{nX7!VFs?Oav{?f9ngpr67^C2E~Z z!1gLvj3ETr4t9 zr2h_dZttwy*~2wi{EvFY=kxZy4)r zk>qh6nyBhA%@=5Yz@9YN=&(vL80g?ZYt%o%Yf)QnLT+7E68$;hbZTlEpHPKL0_*4; z)+BF3J?o=wr(6<`mdzIYiK}esS0Z54h)sdgn~8yT2SYD9d!RAd_m#pOnpzu*=$3<* z7n1IJysSJwN>achK~W5RZ!;k&W9XBsWVH)3ndu|06fxDn8+9%Vrl=&@r~a zv1ppCuuRLS)=j;rm5~>6N=}t+;x-pF@T{zwBFXThb)<2q)ClT=;#sxoDA$oV4M5|{ zvsf0Zmcm#$1q)^aduDINwAg^a(CKSpfAzVtb4s)@VWJD^qmhd38LJYb3E;l~nzqhS zfO!eUq7N$8a=H7>-odCMzd>W`gVmHS<=pq`4Um8SXcKO*;P?gB{wp;L$%$yVWjOyY z{B7A6)nG(UVmsKFo%x!x`P(U4TD&}1z4c@QO0J@>}Yu61~}nq~^}%HxG~qrEWN?C7jJ^Bl+aY-cl(D>ZekLmx*KuPGcsTGdlx<9Kvc{g7nv}fRE5d_roHUg07W{Hg}vk-b9OsfBI8?`yy@1WUjsxQxIV@Gd< z{{U=qMa(8T%T1>i7_jHjyhWL6`l*XbT&*rVs!WT zm<`Aa&r;Xdp1k4j3XXRpVeGOG1}{$?D1)Ymk8+_$B6+raig$nug?m{sY0fC`B?@z6_Ff zchBs$FhT;iKL{s?6Y^3gMat|jMM}f&cj3H=2AF2}1A11a(xDz8Ubv2w>8LB!&lP-Z zi#EwSI0OCuWMwwx)05aR#(RoXol6Y0HhNP@>nRu#f=b zQr80!92rM}DHa=-pg2KqcXFsaLq_)qUMMe=DN4pr0EYZL!o36_tlA{V3*u)Wdh|XV3C{bOP7J?{8}s7t19Ha@ zUUhN=ULkF?fsHVkg-Gu8JGM=>Yh4g!7ZV2*3M%O~xEFEk1VcWiMDQsmJ10}3vR@*1 zN&cWe7*_g1151<^7=n)>RwAjj?nOvVEm|x;KR3QQWV z0rl&&LzlGL4vjqV1L#`EXARJF-(q6Z*V*B}eRKs$y~x7iNG3}yC(mYz87|_kS)ptx zY`#KfP!O)7_}Dzs;Ri2PFWhF`u9!=J4z1J}wFy7DDn2&At`!fXCCk!(WI4(IGx}2O zWti8S2pV1-!pyl30s@H=bzXd220jgqM7;)vp)ns2Nj8Xp2$Owp84^U`l&_`G+(Bf2 zgA{NEiC@xdi&hY;ixTVO)=KL6Q_xiSPu8mg<$3Wy^h2AkZB$KE+I7vK+qTz;k-0Ea z#^ZqsTxsYQ1mW5BI|KNR=O;$^Y6~;j#di*&YAx&&@^|^Wb;=re0F;a%^Uv~Y+=y3d zMgsVpUNS}gt3b~IXY~F*`@`u~A+MB=kJ}C?|@fe)j^f2)f;PCc^jpAtEH?kw}lN8QDm0~HGlpa@t? zUcJvzkB$SGspj`c><7{u5bn?8;9{ZdH|)ux1ZL$w1#8bpy5_D;Mr`qVJfF|PtHK~k zu<7Bmq4w0*Xf0l+NH<5RDOuX`JHkG{FTIv!g||mkxWMbzj=kq-?Q5oDlo}q-nSaxq zw$dyM6RtOJ=B6{xU~q@;U%0}0fY$p~$RAQJMFwk;5bfZ%uI}-;kjgroW(+pQMR+9D zv(7K%nxa1?M@oW<$J`>UZ1UNZYFY}A;wfxGE}-{#2VdlV4d$K-sD*tKwOL?7c>`M| z$URJ5$jX1fNRsX^>(xpPsd?}GKDUBYOCD^JRLAlw99&-23!J-8`ni8_QDmj9jJsEU z$ogF-&H>_=p!_qD_N<;{I+ElQt0i2>c!9k{fGhgeRqDq@o$~qKnvCgkO)zU=P0>e1 z?T68AsGRigi4@~~8`nStoPBkg7+FCPwOA6#U@M+BbmuM#-c29*Bv=9V+>!l)0ZP%s z>^s^#S-_`QV?E(M!HQ+cy3gmhYEm)zh;+|F7~C+b&FREKEZn!^&B}r}^ohSM>67( zDYEW%UED%kx&C{Kj`ZmhRriz}E5elX=e_3TyF*f@k%Z1^09%ko>n;%@011_sGX<1w zl_RAdLqYbCj7hlJ^3BkJ)b@N?YX-z!;BpF9LDmnG0jW&`y{V40ehn2_>)T8Z~*I{Mz5`hE=dFP!wngO3nnLZ=d;KPE9@J&pDHX8zlC)D7LU3lVK^<%V#FML)VtmKwHlcF|zf)AiGgU(zOX-%IgUt;DfUiiXuf_apnDpzm%55%DIUbixXf+H6D{`@XB z$&@2KtDUGT8RQWpRB3N?x&d&J0rVxwHSK|Fe~Bs%a`{q^Bp!ww=2mh9ErwyO(4D0} zrp7VBm`oC9ABxVW z?YPQ$J83oZbf4YogI-4vBAM0E?Q=#>5J@B~a3$eDvw>%@TipT?bAjYFxwCN%2Prnv zH#1x{;#Dk)ii?nU+iDj%C!X7ueX>&nkgJ}M5hqC2^6)VL;bVmM34&zv$dX@Ek;@F) zI$X=g%7uPCy!oi>;8!oMJB<|H7;H7gK7CGgtoy`P?06D-6f|@3;$F>qLLXQKpON$` zR&9uzbjHx3Ce*~X=*iM^-r6jjJw9s}6~&mA(Dejs9m(z9>g=^_wIK0`49*ha%5k!m zeis^_HrB|kYctOxi~I#2=?W~1K8UkBR}v}@>6|XQkIL(iNnD7l3W$<;1qHK6HJ^1G z*_^%_J#LXG{C2Q>0B+=}9|*D|C`eeOaIUcTu9RNq=b})#L8(I&a6wos=$5IcCE?by z&mu=cgdzpGK$kH5$~}yb-$=i%?wPe^|9yNmUIQ$<{Gplfr(AELQo|QPy2x z5KC5}`XN}6%J^QF^!!y{pm_!K^CC6F)h@<6qtz2H!_lQ5-aCH&@~CHNgHrjFfHx)e z?9m&SuBXEF+}mfO!TdDa@a&E0%amtxUZu>M#c}hxudRbNPuAE(*|lC4VT^dr^$dw~ z#n)@KpYl~bE@jP=PYfj76h!`yMami)6=$>_>nADA>~#s=0Tp(5M}Je*FEYmWi4W|3 z>lIBL^Uv1Zv#NGYQ@ncAu??Y`2B1kC1-ugdkS}Ul+tyd)@qO4*PDhX)?|3wqr10Cg z?YZ$V9b)SOXnN{v9Ect93jb`773J;7@Mh#*X8U};0H{bJl-FSVIZ2a3D7TU~FBrnXO$RRgjLv|0q z=TpP4PT85vIHO5kQT6Wp6Mfbhsj;6%c85Gbj>&MMo?D@+ue!@$tp)`KgVo@0al;-< zw`?GL7#<_!-2MpF2X9{>&bSn(56GTmQUHzGEGXYpT1=-%1rlF_}F4mu{Fuc)<-jGZF=Hk^g^(rq^$r;a{H)lK97zK zP=&;kx=_p3!mBx?m8JP;b&iY9+}Hti;$Xz_w`=Z)O80_%*Ah=k{=sUU*yFduHeCOr&b1I|JQTu7KRyDP8TkNr$sH| zqReLQb1^?HxgRgelMJPTQVp9EjRSsa$dy0N=6F{;%qjHJ65!b(DaCe6v%EE(b@G4M z?HgG6fmV^5!{;b8PIVv?4Aj+lA6K*(Zx5Dk1hc~aR@JoKSOGu3{0(l|?omI+Pjet* zP0sL6GBnUP5L}MgRH~zX!5HB^{LaVK6P}9AL1^%zO6FiXyMu;ID9~N2-`4xbw8H(c z>!x5MV}N@{`0s1$gVZx=Q*(ZjcEZ|hfRVP1d47( zxTDNWTutUPfA9rIf?8IW_jM=YrNY$RZ6Ymo>{?o5S% zq{Qg&=5K8>3L=nF5+-1OCdvr5O3-B_8Q4p8Tm(2y%N1k{cO!;2b}W!b-qj0g=!8#& z{n%C*5mPZZT9y%xbuS3&PADDW`P~OaJ6Vcu4RfRxxQA-d0ZE6f$tweJ+uNo}KEtJx zLn20&k}!2XHzX%1D`k(Q6_FSJNv51iB4;^UNXNiet|9<{<;q+Q3{v)9GkbtN-~C00 zgodftR5qE2At)0V{EZO8wLo-HTc=cNIol%_R@24S%_2mG8ZbxpYX~Pb(^KnCyoGb} zn$^9 z-(j4{BGAI)r;zZaqqB#IHfqx1X2J0~T&3pqWYAjV>FYsVha~?37e%y$B{?b#x%@OK`>QE6f4z z4J?Va{~C8OBU#vNE3yQDLoKabA_#eyKF)=pUv2VU6Pz`_y)#cJ)DWGY(5;ZF+LL)y zrYI^(*$yx$Gbw0)%fOsO!y0nVPNsD1qfQX*NDhKy>jP*HcO;1YVMCEED7Y=9Xftaqzv+m729f9_eludw%Bl zI;OVi(dHXt!!p9wE`w#m_`5GIps(!rt}!)uCLx;f02l@Cp@pU+x~L#CunyFbB6S zvbr#O1N5Z_KI1nhk`vd3q%YL?#v+6%M@j0~7-P5R$Y-hxjxhy_%M0PqxjJ5ycG zNuj}(sL4(ik)M)$PRh^;8yhu*kHjTjy7KJ?Hi(V31#-}-xEMB?Asmm($r^DG4aw^c zh`xzanufLHSt1FWKvnw27pi-{@tSZNgqqU(z# zyNNSRejCp7h$Cv^8zTjHOejP6sE1+5Ulet!6ualLvaeXm4`? z15mU-W?Y!F00EE0JKjHe1jb$eol5KOz}mMWmX>SokvGO1?{`%t>s;`XDrDsjH=X)> z%iHL43g)Zcx~)2%&s14j+Vei2&on31*HtWK6leH`jbtd<*p45A^KNVbB#wN=jT1je zIUC9B6sb37ZGA#if5J?__SdcSt?&{_~ z4rTWxsT^sOdjS}tmmQqLb-uDvTW$7gY+f#z9KAs+LEx`$`la0O?D$V2 zVa%LAYOlIf%x#TrcHd>%0=-|B{t|o*uf~KccY6D%;!w zeO5)AP_BXAX{TVK@0HHN;Rve+-o#$6@BQ$ym&l))K;7Y&NX5f@QxGm&+^=pnn?8^y;|ufZ; zBO?=X&f|ND>n88fOyarM<0_5snCtcp>SWgV6ZKkh?pE^q)CXTgbvQv2Z+BLApStH~ zYRO)%y<(g3Zj^H=yQ1Ndl+tmU#I&)G#z7W37Gnoa1hB&SRcp8X?k%UXNud5g-b<0@ zC1u|iISXK)rNuu5r6`1FonDoh%YLA|$ef3o;_H3TSCW%W3-M>d5VANXYy56MvHXIy z#RZtnN=TF6nkug6@gx{)Jl>$ULFD$PyY()(pxpc;H(qPL+5(?wcyf_Nvu^b~6t4Y2 zx;M>>-Jv9s2(lMyM^_tuGny^`Zt|5Kxs!46_F zd&i%P(3CG=Tts-@kG}jI=4sCX0$hf-UjR3F#V3S|KNpz0zKAz$X8aJH`pMN_6 z!$z0xMgVW1uWxtR#D~$A5a!Jg70PbA5kzwcjHx(eO5FqVF0ruLR#W!YTxfEWn-%@@ z0VveeQVZ)TlRxSQ#*|qqz4hI69Sdz#sh1l5Bc zO!w951^OOt&&&&sFzQ!sHAPWesP7G&wf;3OHX<#@aqHksH5Y(@NpiCH$)yU>r~&1$ ztGho-TIjG{QocQdA>-?xoO~|-f{^UxF?VNvqHM0I!8Hn5%kKqgG2d; zUeu1(enJ;Wm4zl62j;Q5+~I!z((mR5_Hh@skA;|8N@IQ`43^P$C37iinpfC6D5~4+ z$a)sH6JAwcO~us|?DVoU`gtn;{CLi&&=u@U#k)rV?k4u%ej!f3Mb!q-mkT@#R+LtM zW)nqH^OezRQiuH5&i)6&K^Z##`_dW_THVn}>c-B5jYGBDgkQVmRtL-7blavLnh9=* zd576L@6$(y!1Vi%=Xkswglpgm#LHoWi)shIwo{UqV7##S=_5&;K}Cn1uCIKxtm4dW zdOr`xz#pq_UV;7fK38m=MMF!xUFTkxTruq+^5ucq;&Y)hpi)_t!Ul*~Zn5b{Fpg-E z%}aXBG&K8aWnR{y2NIebP&~`3sq(37&j$!W{c}@q)ux^J7&($BkOf(Hk>^A3vml2U z{YsYV-VS#oR{KT5(JJYUe^R;b;fsg9I6^LsCF7k`jwpwpwYh99Z_0^zsEblLSX8j>&wD<=i-qvw`jpN%; zb6$}UkU_37dZ6TD(rcQ;2nEXk@J5E}Bi)h1(>dN6`@H;K{~})h%Vo^03DkI1V7~X0 zrDcQLN5i7m8Srax(cSxl-<5P+QBCJ*|AY05E=)&z$6}G+bdjffnKH$(-_Xb8OAEU5 zVCsZBRXy*}YMF;S#|#Gd(KhI5pB5}u2mXeEr-{o;eA4oGkX!goh*W zR!8}!#wIx!+~uW`ONT!WRM{6*b4k%3BXI)24o2WqXcQ34E5$A+Zv-`OO?d3}IhRiB>(e(@=oFR#LH%tg zL_B*L)3K73Ngu3m=_4+|?gn^ zx_CyRJzSzD;Ah%;Jpy(>qdSZ28;WN{aU8rrX<1Pt!y}%{BkfHdRmg48vAWZzU!#Z; zk53L3KKjv~3@Oewm5)4L^?ZN*qyJp{%k981_^}K5U@N5)nE*M}{q1v-uR!+dH}-eV zQpRF+Y&j?6%K#lPu>ATMOI~g{q|Nd%LrJ`w7|(T30FCdR&*C?XJukxxU0P@qm{-W<$anNmU`F1NGl! zKnt#n-l`U%qM`vah!DxpoHEFo%({_h7fUS7nc{{`=VFAu(l(H&8nR#^`-VY>-&CVw zOJhr%?7j(#S*E!D?TV>t-smNgrkqkEhty*x*wCrI($hck)8T>E?h3TgX?TS{{?V1E z=v;ozG4&X`oMl#KtF8~O>a$~*)`sO~O#BSyt|0IpZz&i~ZY_#AOSL&G@D^9J#rRVL zm}6OaJUcJF`xE*w&H9Zd#QajnAn#Q-mmS$asDGI?-?H-py}hNgwb>dC9AQ8FB5Qvg zk0&}Uu49IVs~z(jl)-`=eX!sD;D23HtikKl@F+${(i$p)bTnJCO#dT9rym$mOKS}s zs5~J_UMSy8@_l2$E?DLth&Ua3c=QcUT=is5?Ll1||E~cGai6!F;KL^%`#}YVV80-; zzp_fTye#)xa}dq605a`5C$%s-5QnX6H&0&#(kr`rX!w`*InU!>5pjsK(zP@$EUqxM zTv8-EH$6lyJOsqxF%ntBf(S%)F0Jw+!oUk-Z5!R24RiwWf}~{#zHD{V6+n(rMQ0Nc z;1U!JbBcI&S%JLh#Bqk8DdvUI0|Np3tU(K529kqNm0}Dwb%wxEN)OuW<&+S^3cMAO zqQO@H!+Y-09jZ~b| zrdY}Rsj6C44zU4|xTIZ~*bFum4YrNow5SA?Nlv|bueo+{>GKHq(VHoLPoiitfktY0y80r>JaOO3-mAj z1uqQSx%SR*>WP|ST_rT%lN?&R3ob`*jBIJ7(VbExPeD+2OTw0_)Io(Ipb;#Z){(;i z!g;4`1}Tn&>e-8MShOBIfBaYyZ;L(#my{LZ;KJAP9O;pu=%INqexv0_pruPiuZ!zP z|ITypqJbu1*DuFqs~20{b|R_Kh82y+LdPxgLb@e3hsIzh2pwTkff+#(E2j9J+laO4 zEiyzpurE_eIck!~6NN!`E%41`@Vkwng(BZ0s!LO)GpfzV5j=E?pa_z*SaTx!Tp<1> z+8%E2Oi`}Y;({7s_(}ljq-d+&`z;b8hE!0`hL4$)ca-;cgHWx5Y^75KIEGGVWst`C z}tS#{b(!@OBNDyyO1H_1) zD&ZgzO!?w_$;>@KErdfHmQ+SdsdB}tEFLGS2Og}v(&2`62m$Cvg0o4B!3-e1X?}CL>>%Zr3&!eLC|I{n3PQ|NET4u>T|5LAky7%F3(tNtX zW!45Xjmok^33s;lwZ~mLdCIIZ<-sr9W}-_PN25+>(+OG1tO3AO(SYGuA`B<8v~c$%m%<(W}BQof7+L z5zja_m#1aaVceN|&VMRqg`2r!ga|Z2L|B^Wk*2b{IUq{1ugyGb;a?JL1xMY1a4Csm zP%;YeHpAJxc+^1+nDAK3)!zqM{0P!B*uFyO5$kKTZ3S|*!3+l5crZX7DdzG9(kACx zM3D?(exr$E9|TmS_a7u>vt2qsmugJ%eswPjt7}nS1O#{$X-uc&98rvmz9<=@BKVoW zGLH5bhZ;Ly{8_Zyt3Y0rZlTk&vkzErbN(zV-?n7-bP=@hc0!N&9Db|C)tfw=;Bfw& z)~`i5`*3VdHJ%&Ga4gB6UB%d2J4>e9iydM50Phlv$5nsYIsZ|s`&qn!h=~v0vIWso+h*^~m3u(gfQiWwO5JaNeS5 z#;o_&3}+k{!G!76#t+^*#IzBYaY}!33=zO=#&?$A&g^5&m7MjSUuvVRLp~|c$+nXn zG&NjVz^A&A#@hICe&r3x;~CuyDbs?LXSLBXbRV>2vOMP*Hw6#wx$f&{%RmKgCzn6f z)&SzDZ3$J*6UKG=0;1(M{Eg<~PnLbf?wVo)Do4Mq9@XD1G~E3ocq5MXGi&QWvE?B< z8$7vD3S-+6uZlRTxr}Ps><668`DD6^;LQ~I+UoU1*s;{kz_Q>NhGK8Y&I{6t+%;<- z&CGA&U*efCraaw8cZl}A&Ftf8Bv>#TJCb8ZxtlkFW1J=*+wHzJfTS4r?eIJ6+;R^S z0(A2@3=y+&?G>LdqL}OYtwjgA2XbVMK&0VC!C%QN$Mw+CFUJ-gRHgrcplp0U|FN|g zlio=|*5i!0P$Tg@Ewk4KK2A2Bg-gyq@rO&Em371ULLgJ+4U6nRIx#qfo)pe{c!WIP zWN-dVitI^~tmZ~NBDI+$@x^Pxj~xq=b+Kl*Ni@AJb65nh&%iy=T0;H!qG8cC#|aqw zwCJ$kye+!5;Z8bdhst5)7rGGwL`$VCQ^tJpF;W_Thie(A2aPw#ZW3*zXI;FPx^-BQ z8=8)0QXLA)*boJ;t~S%Fg4#SRrklB3mkt_uf*!K zA(_7}3^@=Z9qkutIqS7s78u;}39T{*UM*s9<4KQ|43T9KHDZXvFqwm)D9hzXE{-(X zf|_T-;Ti@6{yRD$q6;OPsSEOz=RB2jjT5eAcJ06BCtThYg=m|F(Y6}8`W3}heqRNz z3ooRLbS<}r%8vcj1;OCd$hG`GG@WHzTv66-i=aV+ySqEVH9&B8cP-or9^8UE1lPhT zoIr4QcXxNqt$zFVA1L{7_IcJ`bBw9Cr48PgSg)}vXX|uSPcxV+YUeqtZ;GO;Y8>Z4 zH3JsgX#O5FGl8P)Ad5+S%zbO%yAkNv6<26hR#bGUS=Od*wU_WEcLF4(*&4fIRnco8 zW=^LyiRbojtEQu-AUTdaRO#IVdd*JEO#%liq0UO1LefXoi@W6zA?Ff&mmZE)_&Hl1M)%wY3L6|~$w3MA0+;T@m9Fgj;12O4nJ;UU&-z#M8fIybUW`?_ zVxX)rb=Spw`|(-7`JC}~MO6A0$K--bS&&3oM^OZEnLz7F+R~@!HL$9S{;5V(rI(|? zTQiEtLxyF$R{Lu6ie-cGsON_EsE0AImy`->kq2|RbB2SD+?hFQyARbQiXljgz8$FG znKm~u5pUp|dXir~d(}P#;!V-&$Hmpc~6|*xLLVRn+ znc}!yB`;1bdFz_zkJWlxK0r}WZ^HQLDJCV{*wSgPdU2~Vx4C1h8@vA&SeE#4V>eiH z>bfSw7&03*kE)vVlx2nUP^wm*hEuY4A@`$b|JKt?x}Yz?0f^<0-0kh`JMp9C7P~iCqwXRHiScUDhQ%(kkZDltaTZtQ605Os zM}SR{@tF>_QkL`gy&g!-!FogUbVcKRAgyA>dPdGps4&o10(7Rs#94Rd=83b&&Ref- zNQ@OjU2OvsHA;d%;*?lr>jIKme{GBYijmUHWVi5+wX`6f-q9P3PwHOi?YZwC;_b96 zxLA8mcORYaN{*VjGu@tgs8c_i#q=`)nXMjgF(uYc8P)?Vt7cc7I|6H+_2gjqhZmIn zgCQ#qG1?C}w+_h;ZJ3<=%Ccz&cRE^1;Sgv{O*Jn3+$YcU2q($1Tu9v#HHM37urd2v zIU5fyPQ4>wbd1yuNquO^E77o!?#ev&sNT)g>axizRXmjAjH&Ou*w^ZkLE6ywk9esf#H7dQ$_?hRg}a$ zF6bR`FSB`V1tl4J8T0vk9=2_)!`LtzQ6r!unnIN*=7pK83>8;_-XD$6-*GC#N@NmB z*w_5HMO5YiZ?v~J9_%M+wtniB3gX{Au1vO;$=G*%*tZZk4US6}<1IlnefxJ8C6#7T?Ted`z0XfPD{dfypHEyhE8s_cPyP|ux7m{{W z%r&PBl|X*FrNuSF?m;0U3EvC(;C#H?6LM}_%NLN>ej}SAdU+E;4 zKObvq=c;}>+zwOOEE3pzt*&%Q;(cD@CHmr;aKTMan}siRuqb85Jqdv80owPgU$_cl z1c~)1^`f>B5uS*I>aRkvwENtn{DQ|;g@}kz$?^{dVer+JBYw~!)KG?NgWbV3$OI`> zAQ@)I?Wl|8ZGe*-X5IAxWC6??`o_yKq`3-eD|gh!LcM%r^OkGV;~wKjZnyjpy>aAO zA3Y09=$BY8m1W^thW@T9h*Pnnh_I$H0dX76+`D(ZD4~{~6dM^RO59>z1$ExZ=|!*S zm1|!j;bl`n^50xJPEzavOq{Z>NX^`>F7kr{A?n4r(!uxd0awIjYw96Ixr6un$12h} zuQdt_T<1PFlX^4v#5f--5}0@Isxx+sU`aIhI5%AMfk*b;4g}4@TAKQCVJ0>CU89)! z#er-i#Y-Q?oM}8BHafL?F-+12B|-5+b;z23@IR4yR>xlW?FQA`LPLkWHC374&uUE< zZ+?R<7Qb`@IQ`O?$6ilIso!PIk?RA@id*A!H#47&-3gMH$rROyxvOi#pL3 z8`^e{nDNC0n=!DPUc3~QVeU8GE^)gz5~|>H2~46mwm6nmC_15c`l_dtoIV<-q0H`> z-c9-E^&W8x70jrXj57ARF z7Bw6<0)KP&f)ev^ocf7_6E>1Y!n|FPE)mLVbP(p6CvMv9c9XPnH)t72n0CU(b_ySU z)hfb6Z+xpL_Bj39C?$|Nu3qb6<7O)^Lu`H8tO+r-4+L}Z(ydB16wn5#b7a7^CPG)P5hz5z zU2YW77SU0KVj?P$5fw*v-B$zD96QH|#V=lAo|XExPVRyDgFF(R0ZiPhPz4m8LBNdc zXFnROEGTCu$4UU)NKtKJF>r;%v+D+{p|dx8*~P)9VIj%|@x6p1B8f3(R#W>He})F2 zVJ^`DWT=d@pH;*tKji>!vSAs;xp>?1ioES5)6&aME6<@w%bwRBMkibRTDs*UP9N1U<) zcWyguce<*nzrwUPN7sqgGlFJ*#$m&pptq6FIH~R^#~Xgz@i|h)Lhmq*2x2=eMvvUY z^WYOGG7jKo#7+d&5w6;J>9?%avW}sxg(A&{>eey>z`dBYrn+K|Ma4ku0t03!jV^H->gc(|C0=F zpPm)`uUE%GWP}uh;Xk!H|7DXWMe8A*I!1?Bh6d2SbPl^ua=9YTv`+>pDEWD#e)5%r zrF&OAFJ^%UBsG!cwRHqs_;N#0w#M%&z2QrA{A!qRr3d_wyT5>b z%y{xwKerklx3`WzxdxIS1CP=V(x_22w!#_qy8b3hlULwh^Ta`Ch=WegU7wzYg1Dk$5Hehl*Ef!l})GYl$DhW|;@JJ8-10(>|78M}V_#;8pquGa5D_UEN zLafO@z@OKw)wgEFx5fq|q}MUu=qr)unk6YNF^CK_!sAU(23uP7LMN`C`sY?G$tDoD z@xHOIx3}CzM{~zcKqU1T{K6^CJO>JU)DbD7*=MbvRYKLH+ha`*`132|2C?18X)q_LX~<7a1ITX8-m#$XVgA+zBR{GaB;!#8XVku_la`iKdd1F$i`S7 zHX)%x=x=|FC%aHWJ2X{KGIi$CPlscxq#loU?cT@?pXJ~q9&n69V%3}nqAH3B!X3i| zop%QP5K>Amf_or;#@AI|qz1dNiSZXy@1+BRT_;etGS*%OYv9dnUBy7>#fiwM>s~MH zX1LN;m8>so>A%D|?WDGN1_|KRiO(esP$N-y>J`dFP9&=HBRICIAe%xe<+yK*m48USyA zhzza?n^p99dM#O7zw~u0|e2 zFMh3E;8}it&gpE^tk-P5oTYtM_>!~Q(3dOtYL*^&t|i5RDV*O(n{q_tT~h0ObwHh+ z$lI7%@!}`4J#812xD#&s()p1Rstz=wpKe!&+AvVPN0&idHK)yQDp+(nyZHw=eqk5} zca9l1@BGk;K{VOk4WnhE-%q6(W(|t~Eq{JDWy3?Q5(=`Dh`)I%av)wka>?f)=w=RB4NFs-WS+a^ir-FvEV_RP8+V8*1qL@ymNX9gDf z&WPOUD#%JQfN_zW6;)G7<@sL(6mM@1uR!}7^HG1HuPIN{eEH} zpRa@c=tPN8Ow*VL+A}90r==|lLLo5ond#hDnms~?jQ0$AO?OU8($R`p#90Y3rhHBmMm)qHQVWoI9g$XBR zI{f+4?!h6M8d|mP1ZTr^mR5E%>LR64Zc}t?eLk4F_Nof$Mz|v|dWr$O56n0t-gBHh z-IVS3q*7tV3G&nb12E2>F#iL{RYPJ~X%8Xc*F@|b=N$0RB-Or$J{EuG zlg{SX!(?dSsg=IjXu7I%E*bdo0+Al8pv02#PX{djwHSd zOo*^v4#lpFWxBtliOvY3DxLaQqW0Y}$$_ z48AJY&zU~|Ox030;JU|z-W&=2Jj~EalS?-$;0iwGnmz9JIS`d0FT>3W@p`PsBc1#X zBh?gL!jCJBAcc!+Woa;?fuH7ztiZ1~j+54}ss1`UI-YhwlkEoDZN7VVScCTyY({r6 z((Z_%s-2@?>a0(Yt#Y&$6vI>F$}Ak}Qi&<-k9l9lg;PgjM;n~#29j!IduZwv4)s&H?GNMom2wIO)swD9_rp@0>v+)8bSuGK zNA+=^-B#D_v}RZdQ7)1MIa~}HzBhElWOS(QY_4HaRZLCox4UcTHLtJUv&1`Uy4$)t z18o{FeyLaLs6CoZOGm%zFfr^KdHZ#oMTA>JE-_XNSJ-kWyd?Ull!En$j4HDwLQtTH zjxDTg4nr82u~0;|GLBeROnKvGZ?~C#BngSG7t6jc&Vm;{n2G49L zAx{fUkNk~olffqw{|tjJ`-XM2L5=By- zC7d+(2TBL3Q$Of{1rH;g;LtNtl_6r%SMy>pc+SiCcH^Uu%rZK zLj_F0%0NIfh7*G8seFa$*UxcHvjJsm3+=kxX>rt30yAsB7-2Q+96>91hNlMhnr5H; z6V?;KSAp(1*7(}cKZ11*FlZ*dv6^Esi8joX-`mIes;AM_dOjyi^4ALuu2?tS)x zBS?C(^^wE6$V<`2Dw`tt66kJiM~s!i5|uAGm%-Hzvh$hdUm~ll@6(AAd8uQu9$EQ- z8NKs@T;=c%vG~ZKiQ2|xF@rjiVAgRp($G!ZNA{Ikndp1GpGE2TE1ZVU%&)3N>U#rb z_~&9S^>}#78J-Jm@DIB6ci4~ew}(VhJFb31*EgXmA3HFUTR$)63@Ou#4>S_7=WlcP zt3pB?pTVF;!SUAl^USqcdu9YFykV!Z^nJBnJsrJSNzan5oI

#Z@rNH8(wtt1Kmz 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 diff --git a/site/hugo.toml b/site/hugo.toml index 51e8b886a3..f2cf2b88cc 100644 --- a/site/hugo.toml +++ b/site/hugo.toml @@ -92,8 +92,8 @@ section = ["HTML", "print", "RSS"] copyright = "The Envoy Gateway Authors" -# First one is picked as the Twitter card image if not set on page. -# images = ["images/project-illustration.png"] +# First one is picked as the Twitter card image if not set on page, as well as the open graph image. +images = ["site/static/img/envoy-gateway-feature.png"] # Menu title if your navbar has a versions selector to access old versions of your site. # This menu appears only if you have at least one [params.versions] set. diff --git a/site/static/img/envoy-gateway-feature.png b/site/static/img/envoy-gateway-feature.png new file mode 100644 index 0000000000000000000000000000000000000000..fb3f05e83780cba40a56bd81f144dbf9dccea761 GIT binary patch literal 606082 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 pWvauFS8gF(@PEoQxcj}!3I zo&wv1T{6pnwq)sVdGOVpn}=utIpHs>o_h*2Z4Gm4a1%oFo)MqGHp=IzgHTz1xDw-# zyMp6;;2RM|=M2mhvZKD9uxfj{tzE}Csb9iZ%_0yyoP=i7a}o4Pa7NK(lJ~cT$!Mij zgzY^Uhx|$(;q3p4DTi6bS=8S1+l7nsr_6$YV`LiPz_2tv=ARv>QW%VHqR^)XRV`m< zu%I3Q1cu^4*blM(C!Dcxl6WKf09Lp~1lC*OFNwhY5c+c#r0j~TBzF76(39^3+~Dvx zQt^WsIA@bqKY1I&@O+dQmYVFmPoiHab9V+v{rKSheay@Og(HA?jt@?^_$WHNk@!Qc zu9)BPHQYO&Fl;4^{Xu!{nIEO-HwJ1E1mj z`5{rFf($>>Lu4rnJy%OtR&5&o87|)lMlg&YD4gp%d~$C6Ci+whUgn6heN2Y@D4dgv zRO_i!QC zo-?W~rADSR6eFnc&78AaCiMcZ&bgmk!MZ0Vn^_-cy+JNzH_UJ!HjGyJ6gA#a{@4|+ zd(ye?dtLN@%wH-A=+8d}Od%s2?xG%zq8YUiUhK zX2D;s%dP9*>#i7#TDJp+EtMA#i9^ztYru@<0z%OimURZXe=K-zOW2P7ehD!LTK~Wc z2yA1zj;fmV9pcp)dEiphy~e7se2>PRhN^X>vr4H0mte{}?Bh;ve(A?<*%AlVVlM7l zG|>CR#fR`++QG{T@9*qtNz}4JZo|P9;$^nI$!?@ClspheRX;e@9EE*Pvs|}wZV7&o zxOVuVgclaJ1e<81+rs3;V9Z}d&O!Ib=2Nzw;5E|XZ-*Uw%7hocYZzo1YNs>IcVK>S z2P0;j99*XmTIe4cvP5gMszz5V?*evHmt)5UBw<9PF;Vo@vsn*iJ@T zXFcxB9(BmelA&MXwyoNkU5{zU%y@FCy<622lJE3u-;G8JiQ*8c5p6_P^g!FxNO{5c zS$kfOgo@>6vpxj>TxgmlYV8xX{>f1t(fR0ZqA)8UYRSM8jV#FTjLqNrOLgEMA8kf< zJ{-mR!j|E2zupWvnDJow0Z9J$9vBC}1ZP|HC(v$7hxj!pmNBsbntjdUb-^XsG3*B~ zkS40Y6F2v^+X&htTe)#&&}7xV0oB?4+b7?sPZSF``+n)3pLH&fQp(Gd=lH}o4alUL zMw??KN#$d&UqWwqTRyM-Wi9|HdD`SAl%suC1(2cMT?wZO5>3EEl3s}nfa(7M{ck>Z z4FF4{Ak;aREGA>_Z95ul;4V5hZ#a$niQ#FOzg_iwZqy2sUhduU^>8i&ppiM)!UVHI zJWu{Kyf+duKPA}M4lBmXbk(A2t(JzQ?0Crnbjfj6z-rUIjIVB*aY)^_puMCZTOWW_ zV7>gNVmq?ijGQ?1#jyCjW4$D{?r<6z74wsf3l=roHXXl4ZV8}D(dQzou{T|z$-(cS zuau35_i^=3{EF0bDp38HC(X>p-26gZd|rHi2+P7sEXcACV<0zFmUTfnVo~o4^6{vp zY3N9QJgf@EGD+H28-c^8R^oBt|K|N%^dK1 zWHK!C5g06w=|@;g6920#MlNCCzdSk&< zue@IAnZhh-%t^!!E+j9zai<*oKP|xfkncOWNuC0eUGiTt5@bc}x^r}j5xO{>nCq+J zV-EuiD|{5|ntbHhD@cF)y3MVf;H4*gedH~s`iMR3#K0rszmY8v7n92jzlBu2vH%RB z@wdbPkMe-~YHeT1yE~ixk1tJVM_(lzb*7)dRyyM4_bQKIu2pzkvu~=R!E>xf>c!qf zRZp&wSG@+q-towSDfkjRm+`Q3gCQuNGrDTD-9e5;jDFDg``mS>1O1>_v3x#Swjz?K z@rfJ#_+#`;28*H(IzBKH;+OO-w3!(wM!qCA3;`AcfAB))t&j#JAG%7>-BW)Go=e_e zWVcr6O^yL@-_uOI z>-&X(=`dnQ@?b&fy-@dxmFkBHSu%aDSlTVARoeCY?))&H7Nt)k z!+$mwkOF{hE6dN?%&W@>>NlMtmIHzb-u{tJoEGwzH{G>-{d00t0yqbgaVb5dzohjVCb=SwUTP zz8yEb7E()aIW-iv|Gdr61edSd>;AiB#qx!6Gnd>X+5PJ~2WH0{mnN=b(DF8x?QP7O4Y1h${+Ol^3|!H zPCQ1mvfWu}uOir)G(gXsp10b2cYJb8evEJ`#OurN4L6L~TRoYJLx?za3K@tGZGt3;}|WJaw63Rl})2@vNwv&s)!Lq9K%NnBC(y}^QaQ5 zcwr#GA-dvz@~}vR#?UI$(fvI71t;rJLpjmJ8wU?c2?e^k?`hDf#IhG34Kp3RRW%qr zmSnx4_eSiV5|sF$?~&oxj8aK&MAJY&dSU~qMw>?Ag~bPzOjlpg%>N%(Zy6PJ_x*vA zLymMK3@xB^2vS3LBS?pm$|&7214xIcAPpidpn!BMp>(5kcS!d=_bVq)%kcsrD>72++7Aq&$qIr0wNVl{vFz&_= zbl7}hx^kPJl#}dg+MY$m@Vn!k!x!q!SrmSLk8l~M?kAzh`&mdYOYdY@TFs5g4gfrH%41ANGSQQWvq8um9I|`AC*u# z#EW^=vHqec)5$;nY@Ny-x+m^y7eBVrcurD^%K6#ppgjz^CpPct?8qG zm*uuOy)8cPTHKo8F^4_>Nksh0&LMhUxWL$S+rbRnb}-H*(58+icm-mh1nWAw3I2lR zaP+7}^tY(}G5$hBp{D(dc+oRR;D_vI9Z#i%6v9$`uvI8V9znY0k_%Tmf$EM*c>o|6 zABc{he3yA3s5p$tuhy|Zp{|R)3m%ZLd9c=~p*SKqPiXMI289qKa|dEOH|51MC!s_!P)(ZFz+s z-FkP>eYPY!ZywsL5nwLL!ApwYhNLz_5ag z25B8Mldy_SzlaEL;QT-YYo-hRDN;dhy_MDP(MKz0$4@_C)9iRn=1!XXgHOK1g&#&= z=GLq0XkQ^7z=Jpoxa=G~jkDlw@~YXxOdY2Tusm?2U%9vpcmYgD(l{WRd?p>&;!oJoC6wNH7%KT#Ey=38A zI^91>HD;G-iN7Wz4lN&?ZVxIqXY2SFqbu`f#!=AfEBXEauEwhso1#^S>^mE3YrD+Ur*Eb%ntv5 z*u{uHs%%OeTmfUhTZjYF1pnpx2J!Oszu()*T&45-44T`+7ATlpm5GICm(XtEDk64A zlwxeb3h7kh8uiGrf_JB7fNV-Z24mt8Ebs)?K$vJltoKyt1orH;^>o8L^>^PBu3DUf z#1Uz)ZdG0EZvNz>cX{~ivle%QI=mU~IgVjoW#P<-#do{6u~_m(3qA&wpjwTdnAv#6 zC|I$z$HaJe&YGnJCHpa}iRE|52ma>+f;L=c@igu6TEz)QaJf14s)vDC6m1{g-OqF1!qUhOu!6AV}Xt>Q5axhbWoS>JZk$#)FA)C1FD)+j) zcLx_L5R}b=<_lOMryQ@&s7wWc_jGW#;?nqdhkr)m^YqqLd{twORBKUc&MtWR|`5dzITWC5 zrh1s{oD1~6pLK;q5*C%?53yt263_*NEZbL*j=IXL$ta{1CFBugKa_gBl>FiiN9t&| ztW^`PL96Yv9F_Fk#sPe*ymqp}Pgpvu&!cF??674;Buu^b?e{|?AZIPGmk;iARlkpf zOnTd+%@c@=cFwR8OI@Wi?K9Do(#Rys(&Zl5qX)f=k^6Ym(u~A1TLWSr$lf}E(yCgj5@r9Rt9C|!gGA(1fT8WA^B=|l zgcG0fod-pe#gz{4uF*~DQ>g!ivfFmBZiW__>@oh+X5&*AkLjaIcv_$$5rDmUK`HQ;kkv0w3zGK~KVsL&eeR<>kdT?+V7 z1!fH5BbmA{+SlA0O zjtzZBT@o!mG43pYabNb*=507WlWq6I_nWL+6s(;v*{sY+wk(Bsu&Hj@=)Mm29hsle zPfFa-ch3~2NEDJRKTAZ3@gD$$i{UIn@mUV_SGm>5vwh4w|T2&hM2675}Kpc>nMLgs85kLD*$Zt68 za%XLNk$xTNTIsVxy=j<}(1uRCcmAu!59SzCbtvA> zjesJ z)$kB%ED^#xC~E&3*PqG{rPyhiQ3?gD)bJ6MeIwZR_5=3D1N&hEm4oUOwCRrLGM`G# zS;N+YpWb1G4%D$YGRpvzG@Yy^cuLf3)|x8BXz*f1M#r@ihLW_8p4;^lp3UMRbUAL< z(9vL>vU!Zn$<&d9uUj?Oy7dT0nWCp5@jfBO^|Qa`t;Vmibz-St_#0w5M?kHn zPj)7Gny(Neq70ZPO0fmP8`>4i>o0&@9 z#`sNzsS<6zLWwfUlKC$``K1S55Z@D4)8W2zjC&y(I}FVN+AscM(N;KR8E;6xANChG z5%|K$A{hkgEn68ZGPtoywZGGs*WHUZ|M%V&Ke_ZYzP)TKZ9)@jh|xF62k>I?=cZeR zhK}}{5|@%yulc|!{X5!SPoO*zIbBKzAL3>R-EOF}%O9`;J^H_r>S+zsvYm4t;1GSV zXB}tfC=FV3KMx-ISlYVW*2bmZFhlI#-!QgLnPRMbsVHeaEvGjVN!?`fiMv+?I6LE{ z@=$Utx_QX~NW$%l3bavoQ7N)su&9ldUAcFDlL0%hDF#9U`Q&Bd>JX}8?h;yBax90kHPB}j7E#Nx+hFtv^1@!kir1!(r(or zy^b56_@X{lD{i@R^0N^`4#hvyi=Qy`3Etzoj#|Ifv7i`{MMEFsMhkh?c3)ur`DB^Sl1gWYWU2FM$Ix28&km(gZ7x7 z_P=B|RdZ+PlX5;V{~VU9Z3fbG{DbzGPu#ugw6+^fV}#bI&Yg12zl$xjGh9i1aNYOn zQ;;M2UatuGQTCo(TBP1*-gcMgeAM9do!`3(Mzr}+vRB)&uK7X>Qp8ez}kb&Jw@N#pYp9QHz_if6_%-Vq@>mF&Vl*z87E9+4^Togd^$5dmo;}qF15vInHc^h+ z%VrNJha3yGA|&~}l;M$D?WuZ<05ICGH~A=aTF)9q^5&zY z5s26#8{m95C`N|m1qluK;>hNFW-@R()bd2=KIA}7m$y~)v4I}lE6W#K_yw^}d;@Yy z)P5^`N3=IZ#EX}4!Rj&D1?Ik#WlCZ|sxd|Gmi7cmuZVuD+!IgITiNo+kbD9jB9TFv zDM!kR<}$cJap9M$)t4u+6pv_Drtfz(s7#Z(ms^N?PFSr4KcyuMfA!ID;EOKD595c| z=!|V+1;hw~}MUz<8Khos}tFYe_RHSDprwid7?GJE29GzzE1c9KuJ84Wj9-ka z8Hkr87iNpUv;3+ak4^H1?j(7+Ld)x8TjDl@2?^%0#p0WBPN{gt>*_5O{?m7y{1Hty zr+a!p)$GUrdwjSa&O|^ytLMBzM7sfB|y@;H_8O|ytb9|IjfHfoPjh$EtpJq<}#2G@&_S0?v zvtsvy0o)%_2E$-*j{GYYYNPE$K4Jj>mFCZVue05VD%fZ4v)l*KNy0q1L&g>|C@_`| zAY``BN_CC+QX!kmXYB6|Wnu%LO>i59HX=huSm%wNKhA-BcRZ(kuA9^QQ$Jf+sZv4M zJTlGo!G^qcETx!TR9ZAz@IE#Ax!J(oeoanN%lI5?uV0tc0a!##7qqxx3*?2!?VT^| z&)z0jg+3fzr@6jNQd=<_P*Q2-v)!=idi^Jtvk!%R`Qq{gIujL;^)#!6$sC-8q0h?- z5KC1khgQUCt(wd86Vqrhy@=tdyckN7?o7hEA%}}~tXDRGfa87FljG$}-Q6+!A8Uqw2xYJ8;i$!Ox~t_| zd~=m=7pQuD!_q%~^YGO|zRVce|0JTISNflfnCt>_dw!udx2nGb-2xR;KD2>?7NxiI7Pxx|yh)-uwgN?2-uIRbhTOeviUmQvf7eI} zmCIR-d1M^q69moI(ulapKJV-^Tirp*g&NiJ5p0umd9k5|xJDDDj^d@f;(e<#`%o1* zETxUnxv{Jq;ab;2@xnDuq%c9(sz^85qfSZsW;y4{xmp$Z&v!DiO~TH+BZvXWWFurV zg^;S*!fcLr>q4|MqsalL-W6sgr2Ltb?i90`-yi28pQez#7?CZ5;6(8%Lo-7<7 zQ~2-buy4PoH*Y+b5y7!^nCo~rp3|pdcVa9bwgPjpbl=im*X~NHlwACuFUOe{KM1#o zzrC)3gpU{|Df@SrCAu}I$^O?V0r@r{JKl**?2q?b+XWt6%wBsZ-XQ)iX*HkE^3DLS zJvSpD$S^nQrXp`^Y*SKGE9Rqo#GVGA%npg!g`iFE0RF^~yhR~ot52Xqr#^){ZTB;x zdoeP{W0RK#uL=G$Zm_bIJhoMMn!84;m~x~FezlZ(D02{N&sCNc@VM6}>yzQY0A!F~ znG(J&A_XKGW&r=Z{5b&m{F)w>g=MQ(Z|SI_{V0VrSw%IAiRf$lur>V>X#>sUmG-}v zOr07dcvkSrfiF|J9n_R5M=e}jDV{vKy>le$&=+Iss5%7kAdfNOuS`>-`|cf+QH+TS zDrg}w+RD)Ms+Dj}%UF9tgVr-z{>WB7Z6OPcqPgeAkA?~~wVNe;9dtQgEW4>&5HLo9 zJ8tPtrXAlWf@X!JK0R6xB-{EVMJwcZnuL#QXqS=J*GR2fHkNF!EnS5bqT7$R`y)|| z5VvfUvmC=XNfA?`@x;`yPr;Oo?I>;<%h&K!lr4tTAQ94~!u#pGy49biHeou+11Ma+ zf%93T^LwL9*I;_te%`igv2l|>MSE`6b&&pVW^D>=YHjvWd9j!N4}*AAh0q?yv@n^q zM9S8{H|G?AAoDhY99xewWL+QrOG79*(<-~wU&YnB(~{PJFVm6f5Nl9!MJfU(FP>Vj z7^_mLWcAq|d&Jf4ySxY8I3l$z%NhfiBh-2|WCwF(7qdthQi`e6=N46D*jlVr$N zgi9!W&34RroTAhlPi7hOuEf-r_8lK5WVYz_+ z2+6n~YM;`bxkTM+{8GZEP4!h?uOv-;&IBuZ5$t2Dh>TPd3+-EuN0JEw99pq>g^RfH zI*?$B(r(QYdu4Qka`og5m$(oL$ZNgP1^Fc3wP6dM_3j*}eJtpJM8kk1M8hfPfVZdl z%64C@EMk@&cDZdlv`z$~0J4>ux6*AEN`VSdVz`5=&KM%nxe>y`&{?qpYH{kI&8mAocTLE<+VU)(xi<)7!5W zlS3v^q`=XbF*3kxh9E!iRoG#RxXkxoUUXRY8Nzm!80c@-#y=ATdGRp4G|zeRNG9xg zfg$hC*MH<8Q5GV;U!!(bBk+84_NubmW9@N3#>|D>cwnIIzv=d0Sp`qA^XPeEqq z)P$hUHnWyKopaodDDT2b__n#=ki6(VzCfkP;)fiXFq;AJm2M+xQh`3&p9rBETAMk> zZ9fD#*>uP$)Nc);*Qw(aV^22(&qAWBd<-4P|EmewB;IYu4S8K* zh{3!T96I@HuXF=Y{#EV7+ z#Maw8oXy;M>%n_N5MI}0T6|BLG;0~7^%PE0+c@z>HVjn%D-X9MOBp2FkRyqQJk2`X zCED&jOtD+khy%rCG;b|K{eUb_e7Ze`m{riAHE2MRUOtrXUfh?nksEV!rS)C-^9PvwT7PZE?c>3^S|0YFhzh7@F?Q*p{@uqE8@J4Xx3cYGY|uEA2c9 zFZt%#!pFdjwa*p-Au=`2Y%L*iC;5z^^~yG@Evc~hS^vXz-EF5<(Npvd!{3bWFTa?~ zUY6+bjB`%E$cW0^lBC1Z{G)tTyjrd#2%mm7{0Nz8lHc^}Xh*F8XCW%pf8JWMr=zj< z|5MasG*8!?0|M=~JOedJXbv`{w=7a0U8EYX{@0!XnIm2G9q>@>enPy=2YhrMvdRrl=p}D&TCBSpK+XXp|m@#M-q)%%qXFjBUVXmr*{MXD4NIY-|2Nm(7aRbaR#v8 zF%=e8$vuFUrr2wr;LI#WW{R*rglJe>f?Goc*Ym*})U;rpWVK6&Z|1q?3Zzv`(6Brxyc_l9NfJen&aOcz0)t;?AB;p zcc;2%2VH#B>-ES?oJ&2*5m6<9_HMsWv-o0zv!!CjxRwKU0$0E1p)is|6hCc2pQ3^n zQou-i(D;EPb17r<(419e!0#cnNG3>QM-AMQ@#tWFT!(Uulq~eGG|e90CS7%Wo1c$D z@Q&+r_UlUyGLHL#_Hw*S$D0%@&Ppr(88ZM5in@Sce$etM3h?vUB`PDs`YbIic`^J0 zh?gJSD&=k;n<=5wnbUugeL!hwxa6&+g6C(IZc3iW4?td~*5w}M1!CP9)(LWN{Yc=su&vjj?`PECgki2JN1)LC;i->@1% ze6v3u{q@2NAPgr<-Mv)5MmeSGNrwyCc7>d zBa^zix2l?YLHmVKPAVM}?`8t8STg_E11EcmNNQo4iWmojC&d?2)uQ;~Z|BUfDq3{* z0a$%Md5?rK8t6jz?vl)jr|piNwU}E%U{k)OzkJcD1AM^BXfUqHR`xd_eYJ9yzXcN=y+<2k8 ztlH+>IN}#Dh!Lp^rwS^}+aL|&>)3k4ts;d6v0f1Sp=<-QW;o-1&oPwZ%ZjMyjY%Pn z@onFzX_wImxiyE-4_-AGz8rPa-3{Ma{X2Jc1?ZI>A}rdThH7a+Ciz3|D;q8Y?M0a_ zYKf=BS>0>^d0ivzSvlj!No+u7;oAl{?Bg%Am?67@v12LHA(Nc0Jl_gj-<4uF>hFbf z3WnjcQe#odG4!Z63OW5IF)K*2H6F2NY9huuH{&l#n8VL@ZTg9w*UCk;9NrO`?kdcz zPw*Ib_y;Q?@uny2ENtUgO`N1Cg6JD*v{PVlAVP)M>1%-(=LngK{1G3 z*zb4rBy;g=;^l;d!oN#zP_VL>xco34Eg;%2!^JC~AhDA%;^?Qej=hKV8vo$)PB+Lk z0dW0Gjp{lMM>Z^3eeBJrKO;Vj`Jn)YH&`yws!jZ?WwM;q!80z)_+jtE@?OcIl4!mS zZju}+W#YS~NlZ^F>B-yX`FwDTf9%_2Y`VMTP6@Z7mFdu29wrftFrEUbX0m~vZ4YpT ze**p6ndDYtnB3166$Bzi$cXJroSXqWc?nXGTKBx~a(a$v*10XDcv!1OBwW-v_!#?X zDUL|txaPJ3O#gh&3=Rvaf87Xne2yYjl=Pv2#gP*7 zfCsMUaj!e*L}++UGEvt4A%tiT9}ZT5KTIRk>q15Ems|3wsgd=H?1rbf$_po1iad`5 zW9z7>(H!5MGe`s)k&c8q22R^A{2v{sreNlLEDB}D;(5I^$^JkvOq0zGqF5Xv2EgC@ zF5gW?3n907AmOj!URCmKYMD3gr|@!XKo3Ryi$c3VD>HvqAkl9!v{gERsC$mE3t3wOI9c z?bYrPE$2c~N1X%;{_Hf$pT8)@@aT$3f{VVNDB1%)W2o$R0J zKfFNEQ)htI;EAF~afX2Gi&FaT6(;`PjS_PqBAMhyeNLG&OYCGw4E-Rb@NZl^{wYSI zTobG+YrJCyV!PkdWXl+GtzP`!2!>+qYsp;XqqH$YCa5P!(m&A;{^Qs2SeQXxlrHkC7)i7{Cmsi`*aM>Kj(2Cw&zSG2Ef?JCHu#>3o;) z!+10b6xt>Q*&-*E)=HX{x_tl<^Z5Rx25u*X^Z=$+6S14?o!s3KS5=9)lzH#V;+P%z z|0KAxEVFZk&4B@~bJc-9KV(})FAn}N-uU(2u&`90bl>)_J1tGENyoQQ1}39;Aorpk zh&p2We9wU6EAl3pESjt z>_B){cp&@02bL+gvuc+NsD&~5!3&`7-lcAE8iPQ~EZd&%zY>7T8e{8YUQx12W724msj8KhkxH|#N=6yUUIhynh=1p;3h z;S8V2Sz$gT=GuUEXP$ug=OUfJ_t0b|-PVOefNCR?&)ozY612(*JV@JG5MpL1MjeEcf{AA&z#8Il{YeR_Tc?Nt!Z9yd%_{yzBb zif*C4wJL?ksLsKGj8=GM64dt3|Ee`ZOo%2geEWx<8ID2ZHinhQwVDI2@4R^Je15Ph zePwr;Xkln4ISJTfJBR;hDYo0j^ELtrG!?~1(2X~9+-DFAP!&*H145#}Ai}X0Y~n)b zQkHf7=C`P_KkDK%Wi4ftza~#1qI4)7;N=T4KoVqGL}@Clf;>5Zmd#yZeatdAVUgtX zzE-_3{+ZJHs7^##?kiWg53DdO9#C}_n=VFQZ7e6?zbct_ZETlp2@^Rv;z0zW1l(TS2%*o{F%bnI z-r-LoGW{D*O|_!h#I@~)=audDj$&?oLk&W`ofh9p6*U1BwrCC6J< zE6!O`{}(W@*SJP^xfbZxJaq9#x)CSGawC(G@n*j#my-DkM#j{6i^XuLKblQaZ2U__=d0)anZy)^k-OMR_jh;hv? zWeQ7=XR9 zM#%&)W}u`kM8^E|!l}WsQ_Hf*xbn=WK_63zzmM;fqa36`i~fE;jwP3PtmWsL%L%efUGV0Hh4k^&H0BaEo4j4ECUiRgX z;#TCLf1Pc+>mrCNp%(b5_j2ah|FmIW5d$}dal91Qd-i81f6!kbu2*-K0z+JzeXIZJ z4}eiaogc`>cvt^l1j89l=Qr(-vX$hMHW;?NF5U=U_d}VS;5$}i-v(Al$p@*=^4mX+ zEOni=v;`oR|Dm%Av6-Mc>T%gzfKz7{ph*-C(lCYhOoSC8fOzo4#1G|J)dV-gQ{RII zd>SV-_Q9qhv;KtT1p-PMKI+WAh(hokc%UEua%zHayw#eg5E{jEsW30eareW4KeGjQAi48oGfi3I zUOj(2IRjVd?x*oB!8K8r67n!plhc_ba%R>1cGvBYlOjzNjPU39AWwLO(7@7;m5dSZ ziGz?m*6VLKFP`eLn?6$qdD;7EY8}>>rZ8peeXD8#`q69v&!LP zH-2@8JwHgZ%k2yT%8(@G69N~Sy_`HO^XHgD7Y*3Kd|lgtE6Z9^Lbd3)eWLy5o1?Hhg%wmW;3sgYd0^&ic)y-oK#K0{GF;A+@&clfrE z2)OwpO(b%ye`W~ymug8^M_ufL-ZTC>5&IXk>SZ_^2aaEDUN*mHDFW+$*U$JKYee@+SCGt@-;_y? z@pbDhmA@>7f2~)zYnT6B43oFbt?Hl7D6xYE`ksjl1HFjlDeY6Ni4qwXbKFI!Q|mI>l;DgEYK$^^Yb{TJql8r7A42zF;` zEqid}hv|x_ch1IM9{Q0<&vR{rL_@TeOh-~!`5tuSLWg>T_72U_+I)^wC9~!X9`RfO@C3p`adDe%Ks&gc=m$m zL=J#+de6@7jCiy*=r_R=MUwkOYicw3Kr-+Vmh%>!`XN58tM2R^dr{Uq!UNJqWP?*c z<_rKssnxfrLxgpv`D-kOF+Vr88pK@ffZju;5qToqne{`oOUhbI9%&PAWv=?s(mLgy z^~~K08$LMG(g*&@$4Ob;+~JRMQp~i$0$g-iZ&(rzLCHJP=)=S9fv25wIsv@dTs2Rv zWyJ}WZ^I(&j>u^|UT1;ixd9q(w~jB)Qk29@N|p3I!TKT(O|~Py$yG>H$@f@ssaFo) z-Cra)hu9(iKBOhckH^dZm8J(Ih+xk9T@dZ=6LxfWupFbCpTbDE6{H9Zt=F3G(DG7s zUNO57Lf*}*z9L)=2t!gV7*QYC_zl^o6Si5R1V&2C?c`WysI-|(P09<9D+k*Pf<38c zQmxF&`IlSXD{o=L+GC<*C`QYp$(pYnIy) z^K!rs1u_E4&&wDr9$vP$hu-~qx9+Sz^F}aWU9_r{<9~Gm%n^nM(55$BVBQ3SChxGQ z@`1<^bOj*^cdJ$)x)k$AnG#JEAzl{8!l&6l3%Fw9DSxuwc(85&XVCz2#fYnX_7_l3 z!KVlcgrOLj?UY~wlU*C;;}_bSiOQq=$saoSlY1MHl(Vode`*GlL(X%lx3WYH3QcfD z)Ma1Yv&YzHy`J(2c*?y&OA&EMH+O+>U%rIWn+9ZxY)yy&%#9Ez$CWB#)OGZ+sZ!qB zN0E*?ly}t{EvLNj)Ow8T`WE)Hyj(wddry8N*MTq^Jqp$+L|vGz{b0hu`$OoQc7O7$ zu!fH%&Ec=O%KF!8>3WAlFeULS5H}Taqedw@p30~?kUCcdr*-s8;VlpDj}aK z%q_!tTBlbQ((~&!GSrX3Jq3kA(=+gOec7)+o;}09?l*_yPhmocNXLWowgUuJvu_QW zh4Ki*s%a+oI3veh8!7W)HT?Wlo(sSqd4Z?f@pRnc4n$hI7^uwYjc*pd~Fe>F_7pgGw^Qit7^3mpGUxSP%n1}pSo7$M4PPVh^PRo`P`DWZ`Sf>D z`+{(~wP2rF*eL-!j4R=4 zvw(jcNEY0s?-Cl4*THK5)`C|o`kr{!;rC`qgdI?m_wZaU>z|!MKv=0cUve9d@+*Nf zNa9@-R&aOG9J9e4N(Zr=c$$xzXCc&V;nqoWe#O3D%_AwT%2Zoq|a_V62bgg zA-++RlkA0K?%U1am#H4|*#YGkW+0;giyq}A``4bsK1eSY9Rg#pt?3Z%TXC_jrS{*w$X5cEcSY z#zX9VqDQS)!u4DTK`bxGrBalQUBj#pNk)u9x*3z3?SyAHsHZZ&WQ*AMl4#VNd=x3z z4c~y0o#m;e#*R0i>j5*_#7h{RXv=T_n3W}Y$$jpNZ7zQCpksfnynKZ6EUWepxIwO zE3C7EbjHhc%J~JQIM}L=M!RbBN;>gLe;6tA(405U%{%Opbg_M(3-7BONPL3}WL!f& zpwHXC&d=n})}r11d+6?U?wGCARQ+Q*5MWg8)1F5ym6QDTqj;G%^H{7u`Pf=P zO8?x>L&9AM1(u4xm7>C?znlJk`F~mf$$S^yP#wJQB4-SBg=*z@d|7^%Yc#W_hI^FF!d3%mEI+BsWcs6DvF?QaeeeA|ZlJ1_C6>)8b}a|* zlJqXhxIPO=J&P>0ZI?vMqd$|{gCvSt+dre>TFKduBbdV@KUkd_a%jHCbFpR?p8?&9(atYJ< zNd53d_rH6ucu_*i;7?<8Yzb4H26?)6R2`xZWk?~Z)0kH7LD?`=d*Y0_Vi?t5yPh|5 zDeE2ngLXB%_)3P@M`Zfvn#*pYg_7tcl3f<%64<@?|eOk64$-{xuc3Ea~~n zauD8`jy@0S%~DO02mDMW7yeXQ5d{PsF#>Z`RvK5sA+00YR5SRe-T|L_n+2w~{4x}8 z8&hfO**TFD=oo=kSex|KUav=&GcS<;?MV0CJ#RfpY6+&e`Qdi+WAWR~`E!4ho)~Gr zi_YPj^TvM~jwa37x%VbG|Fh6M^GD^Kfr_({n#21Ehy5YMA{(*y%nwCWy++rgwMn;B zWN99j{ZC}(L0J0XNak1iR~tULUvb?vWmWyN{j*n=g0orKIJB-~VESq22Z6aE$SHp1 zn1i?MDO+?BZ$A|yEkGbyE|j6eqqRNOixUHkh_Ch6mmpv@eDO? z--U)#)^vKgL5z@begmgiM{@c;7ynqECYEVMISUsWG4O*wB7K`jO{|-Uwb)|&tYK2p zya>2Woees_I0aA&x2y|T)wN5KKJGZA9~5^;BY`O+so9J<)det~>nnO!hwzQ=Fgy=+ zj(BC=#?ue-EV>HHdW4^_#4(&4j$3tRG5Q0B-haVfn?`(oy*MH4lKG`&qPRA9Mbb>X zz{LCxVBsYbhX4Es8t~n=?jEAN>FlxY{}?hlKzULHbUT|PO^)ek6lQd}AFitFz;sfi zbB;leTg#=4BOtr%#Noml@YXlss~nS{qKXS1m*H=TB&qIab8a6FWga|WA7t|qKOscR zjzHV2iq;0o4p);i>En~;GdaPI*F!w^$~LlsyMqq;{g)e5nGF4B_djuk=X!>%#JNhJ zx$WB3T;esqaLZqAfohWN&8)ts>R;U&7+AY9NL&@XJQe#K4cbxg8hD&S{@j$^WHz4c z#cuo_^1m`X;@4&SfnTpmI_zselQNNpPsjl58wwgwUyBvbWJ_(6# zJ^m6Gqx!^XF8ozn2clPO{I~D6S$(c<8+B0!t&{qTcOiQ;hXdoyXa2LR)%^h{ORraX z9v$ZIX3WrDmL4-o^~QDs~dtHS-=z;GRIkfH-JE^xg$zd93y2 ztmT?{YdK<1Y@Ln}*rKOJsiUvACq%tiM}U4Dla6qOjvW7`a5#eaSk~R-< zS(A^+G#_{ARhNH|a}$Aa-UrN!WZ0JGmSunS z%uNaGd*@SzJ6>dmivn?lQam>jDdf56Glf~!`pE)>tReGF;Oyss)B3picyuUc_0^-p zsCSw9Q4AFFhz+l~_nuZ$s1wDE(DnV!PAv`shG+62AeW1N*0Kh{Hzi698X~~7v2sxh z&=EEwv_B%5j-X*n)Kf?xU)%<+_X*f6$unKPAYw%xkjYcbq@+JhE-ViD1dMZec-ut? zZ5Kjjj<}aoh|HQs7CLGqaeQlP8wL@o_+u z`Y3yb+e_&Mt;BMTFT`u^!dmIDv;pR$`sYp~%WgQnuvZkN{G{1d!{gQ3w2c}SxIj0* zsLR4kksgm-XHz<}m*wN0KSw{88)`P=X;8|1qQ|tuzO}E$!M2q37Ut*i{o#-=ulr;Q zQVL%FKTN%4SQFm=Kdi(E8H`3INJxrwC_P$2MoEZtiIj9B+mMn_X{19!8boR`MCno* z1f->KlAcJAF#9cd7oFB>-s2_PSe8^ZX__JF$dK3l5`3Esbu$0l_~z) zJK|?2yufeYE{q_?R5FlQ%2H!UN!!LX5}CW>8uX*Oay985`xmAH8Fb&LY;qfspBRq* zR9pseoB11RD*WD#+7uI8eXtBs-ha4>TBPOkimT_!tRP6A#dJe%__?+d{P^;hYK1}1 z->VzRLc7>6KTaN2n-Yt;o=P-&_2Mw^M9h-}Wmq}xg>9Fa&KRGXgX{Zc_d<%5-FR&j zj5cqsYHiSgrl=$XsHf`fU$Sia+ny3>ADcM{ z1dsQ*9#_H-EM3FuRht`{+NQLQ?*8w9Tt9(C=Zf1$-wvufly(Vs2|l&q+k?}Rxb#4v zA2!XuoCp3fK5y9yA@d6R-#^GagT$1f!JlUbC4Bk7Ndb|LbY{50QwSOu3j0XLY?v5O zu7lHe8O9|(+b%RK5gYIp4N%&a#08?)%!<|+q;u0C&hBa44Zrc`F?_ zfex-*yB&NF1%`v`4Mq<6wJKZ@sR zWjs;_7KebAVM3H3e2P%SN+9|3a(aTXW^BXzn?zz%&~8z^m-2*Fu9f3ZEH3a4hNJw3 z@!#YU>7OxXMQIk2mEWXNspSkm$PXyR;5o%-a0l4JwLSj@t!*!q$-^LIZ6;l19P{al|Zg6IJ+QD%U^rfa(EJLgfQM@p^gc9=*(ov@ z_1}?62A?|6D~p9(o%CTlmd4_igB`Bw1rV3cefU=v!uAe(7at=AmP@fg z5P0Q=eej3v7cTpD|8KUC0Xk@#HBbderip$@6jftE7)!U4+YO4G-{kwGTOfRgPGL6N zKns@Wmz~5}@&VPusfFHw3aJja1+2uG*v_&%lgo}U>0k-Kn{CICGJ_eSGCNn*s;Lu6 z7e%1M#47zLkqTrihz^b6o1!`j%7zva*cm$^LlO}saW@pk$qn9FS6T~J8phY)T5suW z(FU69t53_^3m~jJAx|fyO_<15Cw9#-+>4HjZVx)OAK0 z4P+FVtU&0kp`K?#t_2poWIsa#f<~#ocWcUP+JX4)os`@!Wv( zy|%yIPa`Ryvk-_lFHi}4YyFb+@n->1o}UvhT`9>TnXXGDG$hK$@&w6}_bK zYMR47xsHh@<-Q_})<)R18M#LF(cqONHIas=6X>;+46(4Emw)FIudDdS<#>VYLNLqB zSO7s~RQ7)T(MH+h{#_=Wu?M-RLB8$zi_n}KZIYbC)KUfrqh+>G)u!x=S|(EM>7by< z%}tVwsoXDgFwACR<;uj9->jQN_-FbBNOW4GVx$qzADOfzDdcs=EqMiSpMcMNYgFPm zeBktQ!0^Uc$oWBo9_mZ*RiD7vB=(-RZ>QzltlH6|@$BCTTr&G#_V_+o-j+GG-HECA z8uV}8v>ZdcV*l+k4$N);WK2IK7>4hD{t|?~g}+tp>J~tegOlJ6AoKy@ap3yV3wm+@ zb(9tylsHa%1Lc2sBGh)(PYNk@KM}$T2LbDd~PMTt@=rW0*mcBPQ7*0pEY7; zCa6}VDHRG&Q}FV25j#YU%W2J;Jx_b7g3ui04+<}$K=|+?fkLN}#XaJ-6T9KiZ{^0E zgvE}zT~b5yOwSKumK1GP!=Azv{DXw+rWcu5#Uo;I4;8|~-d}I~wG4OMC68p%A$yHO z$ZE}ae7>Qs=0*^NX1_&7T47QA9KdK5Gg3t*h{B@?*pKLE3WxclBI6muPyVoO`jlhz za31-PKbp&1N-$2Z;g?Qu`Gwm$2!)UfVf+mZ$LB|0jDxq$kB6OtTgph}iAya;YCgP; zl$<_khtHsk;wg5J20>Apc=cI#6U)^L^w|Gze_=j@++a!OWJU&e)8ensJ6*;i`l>!`8oLh&*oSh?ay07?2fZ6gL2dz*~?VP9ZtAGpv+t+{Q5X3ShV+2x2C<F+WAFRA%-d@B4NJ*NV{vRE z$rb@^^3mITZ(DWNk|}!0%iJiuI1WZy`MLcFLy5%IQSmKwGmW!zOsfw!c=pJI#MIJ| z2IwXh%z?T9v4ZyYC*$mkhnvn=ax5O{U`0I@yCsH?fXwL6=Ci+Ef(WBB&xh?5T=8dN z?r=4hD0%VqEne5Le(8V5AYKY$o`l7%eWjTF6MVj~uo6r1-;E=1Ir#UsIiuQKIgp`c zNp~zu$;4$bH}eMWF8n-6a}~Ll{soZ7pIvuS51^1^2*qaW-T#dtSv~M-*FsBy7twbT zFYP-_pABa%DzOMy00dk*S?rE$@LF{y^y8;lnD?B#VhsbaEGI+tY9~I(#|{SktA3 zTybc%-k(O@bu2+5RXqEtxD@T9UdotYjbq4ciQaPeQ+hq?<5&mFTZ`QmXVSxc_U&f2 zq?Ns1LQjt&sdyjV2khr!xqpUgb$f=L;q@t(KbR3>@ZpgQeslm8HXff(?~ThE2wyvi z6h87~Md7H%G|@iQLsCPlOs=~)Ecd&xu>NO-kTyL`tW2iChjc-Q&`y{na`cf zdp>at#n~SE85+O7CUEQ7^pY=mkSyZJ#{37oI1ZD)UVa$m8p@`}Y}D-@`fKZI^Q^YA z(vp;fn-F~1ogrD;{zhP#S07E%wpLan|H4AJVq`4zRV(v+@cymA+syv`1eRC7M@?Mm zh2&543BHh=67GT!+t8UU-hvuZELVEdxG9N9z>lsi!!|$;MY_7povWOyBGvCvPl0$PL(smh1{VoyYt+mS$R+3<*4*;3Ar}yjrg?|9CmQTVW1fK41TP zE+I}KV1k664`PV6TmdeG#SIE8L*Eb9dI@q~k|Y zffl=bRF6p-cfUU`SX9eQiAu1DAti0hpg;hjtvI(`oshyhLw*bL_@j)VYJwC5zj$sP zMeyU}!NVdfVl~FX3g|Zns z5)vC;g%8_*S920P{q)&2gTWrxe_C?Bwqa9W~GMw_6nf{M^L(U^9nTUE#HR%&S&waND=krlSIXs#Ud_#Q;tz4e!PJmW^ zbQst?D$?c2a%cO>sz{Q=@`FN=1=^;EkpiMitDfU-9h_< zS9~@DkIX6PGBpOIJ|Vxc9D@mY#lT6EH!CDyk)(eJJ@+6BoDKhTJ9t_Y>4Kv&+8W!?ZvPK`pn$X3WTtLKqU+d?N zxUyhWoh5{c)|j7l+Ud2ov?Eh!dk*#!cX^ayB(;Mv1ucMmo)%?n1UYfV)z6S<-~O!_a4j9~=;nC5_GI}MPS>O&MGb+sD1J9Xh~1}2 zv}|$zB=RDgMpv;)WAGa-%OTX=wllN#>~#E1jpBqeFY(0lKcn_<$!{r1-{+1Z@R27@ z4}2Q*aMK`{5KEwo%oax#!9bb7!o?Ir9J1FLr3GAn?ScjfZ3`MZ>>O8 zMR&bG&Gx(JPr9ng?2HYoKR!=Q8qw@Ak(7F~&O^E0B`NlQ7hgTlr;Gc-usAe`)$11G z-fKmI1pgI=e28Dp#Y-roUv|Hc!_!eeLa^;{n&H^#@==}mmgb`F%JZQq?2aT3j092O zzEAyeVWAPaS1eCVWhhe9^ra`TbWShSop zK6?6*xn>4-*Q*mKL9Jcy+v6dSFjkTXTrh9=X~%@z{o+L(d=$ zGd-U^E>AvA{cgP$`=`Eo^;^_xSjl);NlL(XsYB!GMBn5Nz66!C=-iaIru{VnA_pnY zo@&*9{Gd|KrRubyJ)12nCOR!~>=cOZFpI|`<}pO$5)O_`({Wgw#emNm+?1pWT%b&F zvQ*Of*G_KA_h?0{5ts2BC~1x5qQrXjh+8`4B#{L39`YgMA~{FUwg8MFj3!TsxPc2+ zqeuc-(?su_-xo+8yM=bIWBGvkWI>2-w%);HlJ3IaDZilCpARK>qRC$%{=pCOpzv>@ zhxvU~r+xB^7#veqb_d#pB6Q>HZ|oB2bjMkuU~%e%gj8hG0BJtRl=zwFnxEqupJBzd z7*VLK=g2M1&!F0L-+Mfbu=XO#nvb6Be9+{heJeN+q;vQ>EJqXuIBqBiru0~Ijz2Vm31*+xB1Nld&UaJ zM6`_z;ID-8E&4Mia)iYeG|vNjDqPo>^K^rI?uzNWpo+NA=jm`t3e3u~v=41EQ7&x5mQV7EF2U z^j}A73N1?|)j0PUv(S&}vPnF;Jvp8%Is`xkfK2($W=q~yo6XiFJgRpc9G2)Dnd=Wk zOKNBL-{$sZ39*7&pdUvn3UMd=CNUFrormia{wC?BfjZC=@Dh{m zSX-e18G?|?wUa~^pj^lCB_R*s`UP`mjuqKLdc&+$hz{o=;Vb4k_4l!j8NG&zLaL(6 zqc=kd^uBp>_ob1yuM`VO=;qCK#RQc0@7W>F4dz2P`T;<7a(;Z*>CuOAhH1(^9FxvG zMr=@4E}7L^DSBQ&+~3|~p5M~FcPWr-JE>Eiko!87mbz1_G@b_F7eCl~y zO|U!JCoVFcg_dXkPYWP^)=#J2w;M{5o5&myRebpVU5z6Cwj^P-VyU#ffp~;VYmHXO zv^uPMY%L&tByz<@%xG&Xy(>T};R_!%hKYnpCepc*UR`?dXUtjduWHVZyZ>SI6Jz|Y z0q&z8+_rBEwLUgqZ~dLbSb&xZ4b#rNYG8 z`{SWE!T&9Oh?LT+!{6PTSqkULSA6DKW+yr8Aw)LeuLQRR;G(~nv?x;k=wFNt7|sOH zT?u`eJZ?BzH55<;A*ZaI=cB1RcP-im$UjyHPnt7*O=O0T7r$;WWQMM1fE(O&(D%8q zW_nJ!M$9#*iga1b4J}&0`?P+_1MQ!kfH!K-DCwo8nzecApu9+d=-RNW#hQ>c_0Y=-FWsMWuq%{ zok4$vovs|TyuU7>^nLE+X6*cy*x!0KyBqR?J;B||Mm*w^ye?WAZb#z3;$?=o^|8bd zsT|h0m;war7Fq2&b=L7k*(2jZzNSl|z{TSQ??Agvi7Fg7UZ==?wnLEL$wFKEg4egL za1TmF3$SKh3TaD;Jd`G>{d+sMeeX<_!DMXM3L#eG_EpCW#Nnc@?Pah9)3q!Lr{t$gG8rmHACjz|6b)W88woQErD+AyrAB1m__wOe1D_FdKKivnl@N zRoRRmlH<9;K&#Wtlbmzg)PEG+s=AY^7%^8o^zlPX1t!bun-QXvE#yTU~77L3Y+Mye^I?-W=1%XrQpKKG0u?dQB z0VCl5Q`F>l$$yga*wogfO3<Ai!n;9allDxurdPOMLzu5nqoo+W~ z=WxLIr}o|roJc7*vMtqF3uMLIV=QiNB8L&5%$MyJS`}4_lUpyEdwY|1(P?l{w8#a+ z97*s8pl5L^{L!(9aT*z`-z|I9oYI^+h|la79s5o@DW7;sd?=zG7EWw+HKW# zklMemi(3H2G5M(2xDy~HiXjaqAQfC=rwJ~MMM&p9Lwb{yNbI&YeYoXg+mS8)wR+XL z#dYTsXik^CH_XD_S_B`K5|D!ES-F&806sH>g%*TmzPh?YX z1+I8zdiblND#ss)2ds4uOucApT9Mt<+z?PMuy>%zPk(DY8F;}&a&cWh-h?20TvNJ| zJ?F#Ev8UgMvo-Y4p7mE16StY2^GGCQLD`rmdEm>fU~hB2&&=KChd?s0&sFDj=hGiw zmL)6v-KMUf1um^k0&bfdLE%MOwp89f{iR9GYQoLzk*T0<^uwnToh-aWNjj5}4R;9ZX zQ8(KI=%v#^74=2`SGM<}N0E`-Y|_fNw&VfgQS^g2Gzl{^O^~a1spfpNx1351Bb7aN{ayedtO?Gja)*ip09q1Wk!n6Iz&_aQ#>g57faKG?vgwO1?^mpGGCcUR{EK~U^ll` z!xgg_ZlT$fXyO7sw~GO~d2E#_o(Zi+I6(WnOGwdnt41dBS07f^m?)_M@`vAp!uxW4 z|5KXV#n*7L5TDpKg;~DObpIW*to_?A&$JyXgf}8`f0QO9SW-*kuOoWAil+zmJ>3v$ z7+5E>!WZ)MN~r5#@QCU5Z}`h&FQCRbNSYkj59Zi_2jT+J0o(BRy(gUQAv;88{-^67 z41+f=L$EZ`ZhBVeYFFS~1W6W%hjZ7DAtMiDEDHNuzlU7T|DLajpH9D_&3L&K4P0mz z-*aMa^*B~pH?aBVKn%>a_|{s-!=kuDBCUC58{az{Ye>~H1GuKlb>}jbcwDxxx_nVG zDOH^>5|J2j9>4W=^_z5uZj!SYhx=y2Bp2lt99+Cukday)E=V&w*MADS<)B+tu7iyt z;tZfwf)AlT%}Xn~4_(`{ejO?76k#fBWjvGy;gK4SYC)xeHwEp?eb>Zy*27rDo14}L zM8}Ye4!&qrkiQ8e<<;P=ILJ$>fP?5Pq#i?bj&si6wV>9V&h9QnV7Fds=~#DUZ(48B z5>R#eC`Cdu+hcL>p4^8F-Q_Nb1VAG!l zuIMaIablnIVRGV}=ea;GGvZS|C^i}5boQ+Wj(|5gh-ULXAkh(+ohtBikUq5{sZyR20eq||ft-#`5zdT_9ezmOLhynhDkh7mhL<6K* zm;QSjJ`FF@S^WtQq<1XNAR=s|yJe-C8#x!-X3(2gxfcbo-|(i?V{PxTTglw~V-7CP zqagHYYH_>hx<9$od;X*jKDS^7BE{ml@M5|`3W`?l8}P-Lz1zjla$SkV)wEI7i22Z1ihVOg1XY2eemwE-o9qmM1I>0YAEki^ zZ^V!&U>1wg9fQe874}4R4sXE;Ic6^6x63{?Zc8&4S zWJeK!-zFbw!$lQg1Scc&e{+T#98Db*jvqGad&oi^>P$@X<~keiM+8;GTKT<*wdXe^ z@0NMVauWZ2DQrXW#QfwJASZxV8W`a0C&>PqpCOuM%}@OrUw%@QfSTc2&-_;5WNl=& zM{_|ViYbLnzx^JzeGM3^Ke^K1>{SkQ7*k^x-HZv&8ps9k50w`|rVt7#H|=z1zz7}?E) z21Q+X=g1zKFwr697-(Da*+DEK@)(2>pAHM9n~#KR9xxf`*}Np35m}DNwMK$RlAt6+ zxgvXEVL_B=F=AI#N2)MVlI&UTBExy75)mG4zSt0jkks&?s4vmP+|%C-2`#8bEKVfX zQDIC1Bt^`Q1frD)tBg?M$`20;=Hq#5aNzwL7K_M7F|e}NAPx#kNFJ!xb_Z4U%fvm% z9V>bYpq3}g@9jlC-7qVcCj`TWjs8vtP5Xc?EA5>;3$t_S6Ucf(u?pe7rT4Tc33ceq zU$!x_zOtDrg%zhq(@yW`U!wpWOF2HzbT1;yWiRG&BUh`?DpQ`8j`tX^bvwS8_^QpG zevZeyY4;{9WZgmI{uH?u)i5`)&y{ym9z5iP%?RaSXk{LtP@zs3Z7H>HT5N${`?a;c z^=M-`J@>j~Z{<(vrW;QtFnW74FxugTn0^>dMK0mqQObjk z747)nVi@B@TDRH@FTci&P*E)I_4^gb#!&XyFRk1KRa^@@#%xq??AUs^CNSv#tmns-w|hh^9kWQYYHdYhKn9T?UbAr zpBVPsipMiSf8e#t>8(m}ePVeUU)+AN4iCHxLLWg%-Bz$TrbFl}?{|hf!#Odc`)qWr zQ9QefFS@Ph$GmIf zXf9NBL{sN}mbxt(BHroRO9=%v;n$EF)CMgmkl&*8LT_JSt#Fm3BRqjKY5;u-A)&|w z{Ope$n2u~p#?%K(`e7O}$I#q_TWF_v(VWK;+WH`zc%Yj6#fi6-Yo2=ysB;d3R04;`rlg=hB-SatXHg?z!I+b6?PGKTVA|JL`cc zHfS?Xs?~iI2}+5JETpYsl;ev&Sb2~n>|EN#LJsD#fbJ}dA^NmsaQ4Y;wpV~S!yFtl zH7o(J!6i){01v%%-5CH-%R~wbFGn}15y>HoBw+rjhZ|!nz~Uk}*h1YqSo#=gU+>>W z3yg7u0d#SS@+nekHBPF%7nAjMm4QYoH=f78ZH~5PXRHubj z1@l1v!ZBi}5J$1kg!U0qrJ-S|#vLhp7flNtXJ&`daQmx{NiYvq3JWOLNi}$iI{8aqrpm)f8PrQUQ9 zSl%L4*n#%jbUZC`)2{dL*v+W%8yD31lhzrRWl0*(^OcbzqSa*Jep7l&He`yr-!S02 zt+)|P4(DlcU5@%|alOL1ZJx-BN`mO+R2shTDmW`mC=+o%2?Dk@0i?w_Dy|_wSn$A8 zjjT%=IfAiWW%&4a|A^CDjJqY&sj_#ZLi}Muz-c2@VDBM=nfZlJ1?3MO#^jSc4_hle z_*2U!i)rc~J5>j=5)Lo|VQonv!77;eYQ;V9YD%+JA*}J9r#%Pi3ooY~gJ17oDfY<) zWB-Ewrt}l{iN3|Eu}g;*#1@$LsGsJ<{5pVc^-B15 z-go^2U-WQnzt~sfJREf|yQ}$!_JRCzqOx_K46no=Ed71M@A5AKjrSFv)n*lQ9@c$h zy?uBoeD3};7NI!Hl3tZFV@|Uq@YM_fz9C%ys1xezvN+Y*JiV)B^erB z-9}d7wLrR1<|AtaPQTAEu&T&3*qX6nST^bEzWc2*o6#OIuNiOFV-c!eU0N@1O0( zBsG1Ada9>+J*kQ7Ng#WS8L;U3sZvsq*K7E~-mu(y&-KjKlQZbd5#cZ9H$chEN8U-! zblV_U734KKAe4*RRx9k7i{aU)|i)S}Wh(C#u9#RB1Bf@3tAxc zim7T+0iSb3a(F(I4@-yEsi&8atRs($g|#OsotH=lxyD&SX4qs$sznrzn~(onF~QjS zTytet^EZp!2voy){XUIrx$3Zy+Ml=&iFv*wNqq+v>qv#l{JWmwj~4fuwBKayENN=CjW%i>#k_5E>DLe!Oszwk7_8%;hVj zY&i%mEvUjCI(?Jd*McG=` zFFW?N&}puNmZSXK?n>XF)$!wVz*NvFp$w|#K-nvG1xUiwP2(QJsGW0bC3qvWl9aU_ zALPFB#x?PB(P*KKBrAN#qRu|`O8}M4HY;s&--N|**&_E^+83T;#K&}1Ej2ToC=y}JL53NdIUCwdDtt z&0hVz$IYT^RRL}#lQL)#Bo1O*@))q;O+YOQv^>$S)jwq3q4g_osm=My`Vy{m1J-Bp zeSb_7av{~>Aa?niIk%UV@QC5Y`|@j%_jQ*uo%XIX+pxj@&bg`Ly|bi7K_AiH=W@`h zZGF&{&mzT9UKO3{8h(63rNG{(0Cfr~ywM$6&3|^e-z%_k( zKoIq3s^mT^jniNX9e#8+Ivg%V{RG1m&lOhUgdFkp0txn1Y4yK|4eZOWo$Mt^X7S?H zY3caJYVXvDzGK0h{UbI3FUjD$b~W|2<-Bx!{$RmJ+x1YU{PHZk9e+v6lkgb4x&J}x zpHKV@nr@g<%w$c5i@kC-*Yt~BkyDqch0j`?vU)=B$np!2wS*>yA_Qdr9_8<$E192;b7Y8L+XSIw(sU(BXQ?jnn+p9qt(*&9{|J*@b-B);Eybrys{OG zjx?BZ4<6RlNw|%&9Nt3w;g5ZVNx5|<{4c5j z=gIH4l{fg^;~B<<9Dr?SNR*+9FAc(Ky6=!beLrqlem$3)Awd!X*vrRX-VmAiB@|7I zI_yK;JF3yZY@(IT}z3N8l1gMJt*D`SboefsQbA zK1j8tKvYN3_q794`wqiBWWsp%Lek`Y?Gqth=7bFt+at)K!tC$VT>lR64fEtAd`Lva znsDFJdt(G*TTTElFIZBeg{tV2e~BtOspXynZL!>rZHxn&c|N?+J{llH1^*0Yw*&oj z%<0IYW)~Yk*s>7zx5lxQ0EBIGlcVa#9ktupYWa%s0BV==!aw;7tI{t6}* zp%7lk5f&*e>>d!hTeP4`G~AYBqUF{-R8v_%dt^AbNMY9$*kr=oKyfwdGhT@w;oV>O zEs;7TKk6yN-H{s{$XQ5?iB9N@{73k-K?Yb*B<*?_D z7smQ8KZfk*pJp+?0v1$XFPYm(3KvEA+Xl*K(VH3%lEk`SO?+|{9B&5gekzM9pKR+| zvAXjw`OmJ+uL!(2zVYV3)?`$VqU;NI5E_UQMKtf4jD`xaJ>^p==jMud+|Hk%^mT^k zom;B-;fw@# z4bFa0qUbpjnGO0a9{AEA1zY3CS?JXGZW|maXqlq!oYS39iy;!G(R6hW@s_O{3lX|L z=PVSai_*wV2GL_k-w&lY>kFU(X_N4smR223;Z!%n$&34f7r$R2_ZRKeVwS$fTRL|_ zxrF3i70q?AwCu(6I7)y7fOOI!E zHmY7!oFPvP3b2c3LMFcgd3(nN1Om~2s>by4UI>Uj&O=Scq``hDS*NhPFLUpE!)PPn zL-Ktz02opGiz-r*fz&9I@Ow5JJ`C$x8;x4ZjG zkiLCj@w<@BgVEQhN;)rqGl)7zjRtg>1g$&?(j+2BbM9vdcx%1IyEDt8_p+MtqnpSd zUl+-H!-+ccq1=R_P&yCs`X|Cu`Yl+&NEK?KG~ zNVLRtpzi9bYj{sy&W()iBJY>ZkI*1qij94*ea^a?2K7J1NdLRiEs2@T-ji2;zGG=S zT}N_tr;~ut7?c2^oq`e^wdBk$4hMBE>m*M5b;5}8v_Z|libvb0KlQV6i)F>`y&BK% zy@tO%`Lcmd(e@v9`D`}(PqBJnWm5=6VMSPd@K1~V^Z%y>*f_hw{`lMa<@XwYk?YPk zP%a4iZc={kdlcI4>3*%3yYifa=WBq@l@pI!JsUFiY-2|St~)UiG5UvGxhs{o5?RmmcJt_dI+t>x zEx!KxZ$o-ggNq00c6-|(O=OtE!8g~B5bCtk30O{Av@4AA_FMTwY4?dahQ(`0=?4%= znEq{D#7Al}z#DV4D0kDum{RGxAtuR}usCZfd>RYne*aXl337`e!7rN(!U00C^u(0* zF~Cb1LsjP?4!pB?WN+~>XYA_KLFgY!Os_da0c0WWym~{l_EYI+!{Gvt@H0~)GyF|epXo$i zCDgd6Zz=;Q0$7QFq3}Eh#Ch7URXxG$f$ZTph-3DaTpUsdJb)R@2?^9o3yiG$ztbGf z!+4LBfIj|ZcziJqB2WuR;PH8MxIW)dw@r>~?bA7&-L_ijyBlrt;+>C5U5omBwgS8R zU?Iiv#o6AqX1LI;e5~-;;9ON{38#_*P&jK$(L3wv9QxIZug)%u!0#$m- zC{Wv}BtEWfh9JlP6$G@sKmc+K8_E9hmHaIJS=*;8*=i}RP6A7G#jjR;9ikG}+)Db% zW&EwNMMZ_qxCTzAL$Mkkkd?6!$hM?+5uC;aW9BuzKKfTn2kGyHIvaUNRw;gLy7GJ` z6DBM0pZ*(r2!&K5(t2dhScpSTxBm29ZkJqL=Hy?X4oGF2um}(F&({CZnFo6pWmy_c zd7`rdS54v3^_j{~e@jpV|B?Dzw?WnW+URa|8B!Mr??;4gd^;>~8lt8A=(_VV5M4}p zRKpf|{WS7=iDV_2xLmTwn@f%_jeuez>*Mg1d*Cj^6hOR}mlS0=rPohsdm8UgY^bd| zGYiC5T@f*!Qzqrs#do}~fBUVV^IC4I>b+AKcN#Eag83?K)kXp?{bRl}NWCq(5a28M zk&pu@!2HWeK%$VCoSn`XzuJS`YqVjY6f=oK25vgq$6Z3)K(1Hx5O0yqIf}nC z7PoLnn91olHjM^6L8W+=<+nyAt8(&lu>iu8UW2TKPtRJ~pCv-u!IHyW!X{hu&-TB`=~?Yy288U$j0be zP*<|318mkS8t>^VZG)4DEz5*Q=9#P=1tqo=ZgOD&{K82xt0qgVKuW_jV2yUoH@kV) zOUwHX8Ne1MsZ|(T*Z~3cbR$iPr!$U^@J7bMNakJ!iTzy}_7yz^t(%EOEC)RlmL_D= znnc24Gp|*+<)tQjsF3R>FyDM`M z=^QiTl8<5`&UrA1pH|t8e-YZWfYF5Mjx{gB@7S|^y7AU}aBB3;HB{xJe)Q1kHL#^M zWjncOSfDDgp+;H!_r9kvGQPg<;&CQVGFKmCLS-<4{RA>J-C?y(;6W)i?A@20@ zi9H0hWg~ke>;}x7YlV}R3>7-xr-mdux5O>bD-(klU!-g`3!itjz=N@QDZ@oZsjNnP zg`z~#wSHutPqtK5eOpj@uh%C6(cgm;3&|muf9*Mm4aAIMr7NYXn%Hwx3sAi9VWt83 z45CA2c(cqnuh7fX+#q52V*QV)`Sn}(dp-3fxaAX22F-i&$*WZi$ojGEwO$kd42)Kn|A?Z& zLCMcoSlrW<=(I{iir$My-yTSK?~OKW2g@82|KO674r=-x-RK+Qm++;vG3_us#sa8_ z0B0}=G)vc;vXx_P3s3b4JGXum0iBY265;V87G#8inQh+=sqdyqT(Q|N;m&_uU zARRia`1!4q@s!5w9Q@6Z-p2^2HC^`tUHAM};&*=VLj_Y~k5tOT0Z45yn_V8@CJ=;X zJ%Jq6xeDrwJCFc93AMN{jkvP1`|DK(>9&b%B)W(;E;ImHd&8tI79n(_f~C151S6~r zgdIr4@i1knTCC;v>m3k-u->wOx?ZBc^ErXXfBJ>?0W_^JQsHMd{)oj(YvdFo;mA=J zNmC~!?Xz4iU=vFvEjfBfbOdFioorn0n;<&lB!r`sqHkFqDnmTEgGQG?;Jw51mp{xV?lB_f5CboOa3cT-`8CR%lIX zIw`K*`538OH`4{e{=X30B9Ty@sv(6jhgDQ1=93~JEgv1GvNx#wZqA31u_t(`LMz~- z6#c!HL3Cfwd(|T2!xyfqQnNo(D5&l`rSytYiK-S-V8YUmpH+8F{LsWzRVGlC*azmP zf|^tOBaZ&EkX!!ECk{kkgoCbtKebsNZ^D8oh<%*PyYYWSTp?&YNaOk_aHR{75U#A#(0 z5g@aF_0gZu2iSDFK$0<73tG%Mf{u-UUY*Z}UKtP524na0FU_&zp90*n0?}}Q&Wjn*TK>Ej3Ga`3LIF1ri3iolJJ z0S4#t) z>%0L2cTL+%d=Jc`~HABM4kj8&H0(t_Zd43hlK0?~~pH;x&-bMgwHaO8)Ku zJAK?=iRpK5twk$X(nyrIo6D^QTkjV65z7G!q%>lmdOp;F5>v~-eAh9V6vE^$PRz9L z*fFQdOM54pWwAr;VpoN!GYCD+9Wj+77)`OQo3Q%lnz))pJQ>C$qKNN?Sks)A7x^@e z*h_>%YFu!7M^sz218kOMspA?MjXt0Yb)Z(Im|lprz(OqdF{D(wMyp#?67qY|^8cgh ztfQjrzPGOeLk^6TzyJk?Q{vHz-Ni^`$x=XR^0Kp5C2~P2vN>4nK{DW zbO@ka8c}no7bLXeZJ?@9DlQWmqtgbjN4cwte-r?8wD-SUXYh-6{n`Xh^+|<-NOyuq zH>)mYV;t&?>LS;E+^1q!-Oa4Wz|Pp+C?zE*CGwwTHM6bBz2lb6p(Np&oTQW>rtrJa zMiUvw4LbGfy#MgLLcpZ3S+d8M9d!jV@9eFWJXXvsyty1|r==6LiE& z5J8>>RBY zcA(=-ml_*zr=UhuDzzfz>d8**i=jCyezk*QLG9SSA68Gz{rWTClL4T~c9!W5XDRA3 zg4^sjohMcheYY{@qY)qsWj$-|7ol@}wM{pi3=y`6Fw;3cGsgnSWh*GYcDen(!+oXo3y+_)8$A{k^ZFk(}qq`M|EKQ+l8?`<2vr zuP}6`4D@P^jyWWC93h`$4rj{5W>1Cg6SYlJ1wn<>9#ZE)fa>O?7KO0cP5q}zcke@Z zxoe>RPCY|pU;hDTFG+~xq5}^YM@#Z z>4(}=*pu;ThCiSlK}YvrX^fLHNxYnm(QBH1Z5irIKkB%kB2k(o6RJ}h@J;4{Rg*Fv zOE%a470Ud*L((El9{UpyGXLkEk*w9m+gk7s^lqrn6&ryWI)0ypl#v2#gn+O~(a$PB z0sBmb3TxgkcS!zqO+8+Z_Cj}F7jzj zP~%hCGSnY!K#P6u4+32rR`H^$uxW7c(t`P#C26;cOP&PY6QgUY0wVRlS1b2{YEtx8 zJW)ZRI|{n_i~1YA@P8Z)kVGD9#A)ynf~3k$pSg{Fm!#?t!i_-mKbJpV3WURq`x|P8 z2XmW$-}(nw&sPM1T^b)z4Su7oU-TpLuJc;Gbq-0aJ+TsEF0sH#p_!#D7R26f$>hQG zhu~RBh6j95QE7IrP5-~|S7QpZhJc_zTOd~=gQCG4G5=#7Q!LlNW)UECe05xj1*uw2 zx8MFy>!_Wu0i?^H7G#hS@L+2V88JkLyBe_igM>?FkSC>}uhEWvttA6`xwT>?Xc_V+ZaIN0-A;|CxRjJ`R_t-T(ICKPjT1Et8+ z328r)TN7tpNPjOV-Po}LQ`sRIzw#Nvlqm3d`?K946zQh&HnuFnf1m$#4j$0m3U!sq z-exJ~;Uyv7IEOnl3LBS_@Geipl(CLk_NA8;)gSlUKc96!nV9Z5cV=#+F+bR zjcGIp^~a1GY|uwdfEoQ8>(Wd0p;^!GJrBf4$3}THDw~I9qx(DXjXC^KsiyryftrHS zv22|p#ug?9-dZX2l*0fQkxE>I7nt%kws=5$|E;qOsl!%<<8|*Pp)>NiUEv6~t*?#- z0ERzBS5PRRXtrpmH3cQqjo65qYa{%BBW$7u6%80X!bOlfHgqPQs@I9#e^RIul922!X48+_S1@76}m4>#)v$Lz4V>B|C-!Dfz$rW%AoJ}y7Y@}11@!1BU<<9GA~`6Tji||Ofpk1{rM)) zAC=1QI$YMQ!^sYyX}=-OBBU{)-=dh-^~|+O@$*fMD2!3VlUoT883eihL2?h_ZU1Q;N#)bNOSb zLJjA%e=Fic;sw6P-6rF<2-E~05#~Hd62f$sfKvW3j7AMBrFM{A2;q_ajXQ^c<|1*M zl?9*IMZfcQE7QPn5HyRrK8Ep-*`fK5{xJgc+4~rfsmhB@VT{;H#AE-cdOxdr(${8_AlD`0*2WOKE0qX}m>Aoc0h1LSy9;zUp$8UyzSN3}LZ=G- zUgq3Z4NZ6?qe^RLT{j||8EW=8R}wRIo`7s{aP4R z{mvqWxcR#z#d9+hNLfCaRuFOKIB?=TNL>FZw6=9JbU^XPs#$&ZrSB^ZbwfiFgSwKK zJfnb=W0L1QPRBd+ar1IoPb^F%$GjxOh$LWa#rx;@&y8r7(ouT5zRZ1?HjCF? znyAaqA;QoX5k23SBAqXr^tns68?QLHws6HBSTM313)$>?miSyB%JgygRhjaHz#ot^ zFPZyhP9l(p>CeUhL|9n+cg8=DqfN_LmD?^mO=%zBIOZ38muU7%4e#HNUCDoNvUPaa zxRC@uhXX&}nhCRALwqYqd^`JdvIF#@i4M9T;89C>KbYP<%w-)LDnv8h?^rD5o=pFG zIJ^yphay!ocQ)dTg#>AN4$W=6= zu$U-5IVTGF8)VOkOgdLJQwEUS59hO%=_<8Cx>;vPKSTYx_d)DtfZg~7wJ4Uf?KFqG zL%s<>$fS0(Q3SFyNm&fc9!wK+#D(-8sYvw2vsvWH?VMmQbW+w7(lWt8aaZn}|0SCf z*%iUDyf_?x#A0Q{FQ-E=*$M{d6cGTQEH8&*+uSQ*Kj@*}?q^yb*P6j%Gbtbi4T1Dx7*qqhi-tB&WuV7=pG0j3 z?R~(V)Z7D>qJ(Pm?{>i_H4ev4-!S$4;;C5w(%1ADzt^b#Km-w$a!52xtK72D5rU@_ z_*P1eXhLI~iO*S0;I`gBW&$bhLM+JQx^(X%Qq8x;IfT?$wQHZNDlBo{*R!WBe2XH?~a9QI#swvSM@QO0<;;PIJWC>?#h*ELxM zjK}Q|ZMIQ>Bp^8Y3i9BO^J^MGaCcR&x2*V$kM_I2O51=WfOL_d{9*#kch#OD0f$pi zJ=zKlYAB^(S7S0`P0orhID@e#ka=_O=O39SIUk#DYvyokX9Mv%>1agz3_SpJ9Cd?H zVHEx2KiQg3Ecz|};}zhL)$q2teQ-5U!iT|mdOUXo0%5Rmr#1GKXJMgvS~S^Xv2GLjK)QV2DjdlBHBG4Uc3 zg)-!40BN792!P@MtaOY+tUVtQC8y=%Oi9g?lY##2hX@OvRmKK}N_?Zjs=Kjpv64u4l3 zrw$d(?3&no?gJi-Z|F)t#OEF?BB|r~EYa;|H?zVXLjO{HSEH})h3(>xI!h;oCzI?O zJEcd1B}%D|l={A(e;nr4?Duscd0F{DLjNzlxpnDtaYOELBSG$8^D|>%emhsMeqz6s zK6uRc<>LN3uwJ(jr&V7^Stm(V3re7xNv@j`8eO;lsjhB4UEMqx8m_AQ61smi(<0^o z@=}F+2dXxugsHy$g5%NsGEu`?MHbkR$6Wt{SmBEDc}_q$1bH%1lYxu8r?45_K8YDP zf@o|Ti+C9~Vw7rmr*Ndw@Q;y}r@G0Pb-0KbHv6)c3jUFNY;?azJ^1kElBso6duHt& z&>8hy5bw&p!#p7vsk@19419NAfz3fWo8qqRR^#SYLb|u;A++~dBAuh|%z6HDlNU(# zOhsb^-q#5yU-leFlC#@<^!$ScQn@E~(>Q{_$>@6v9;xkH)XK(%{8R#$j)>R?bf(;@Vhu+zP7G76lxE3)F%UyK4_ z^iL^H`V7L>14R2tB%MDr`3RaU4k=331y_m2EbngeI5l(!djD=TQpq-S((!;~Z&Ra( zk6X$gu^%rW{xNYrE1m}csaWCJfVcW6gp3tM9nK49XU;1_{l%$iX82X9XBanM5d#EX zBMQ#h7PVfXId;(o9$8XqrMiq%N=2YHt}B|4^)ediTbOi*`Ig@Ww~s zpM%_LH`8dzK&F^`B`8g#i#pCcoKPq@kt{U%+o>juJH(cX^s68#!9eJXqa~fLGkD-| zRWKqc3kO{D0@|@_-CAV?Apul7seKwQXfUYjSQXZ3QrGO3&oMoHv*=ch(E!(iG!5{_ z%3{=ryek@UadKiOlcP2Q;0WGk0c}A6!Rf}(|9JsysX`sH$O>>~X*`{k>1M&>h;_N- zODPrV#+Q3g>r+9k9PW4WH5lOcrfL6lF6d1=M0G6$K@!8L=gyIl%tW z?t~~s)d6k86y}O}6XNwI!;fo(Ni&>8>l?l+!FGmFedsY0z|~+rv4v>Cr2V(cQRzHx zDH4!?_d6eTgx(3i5P~|0(JAQcK+Hm{U+q5(9nn~a*OMp1fL)pf=s~PVXhBMrlf@$m zfhUvHO1wm0_rav&=J&q3`~4Bzbq4iRBU?P3x6PbGohyF}$59bVQxWVUi1_4Sz01FQ zhFv&)Kuov={na8%)HoaVTsars@P%KM=-ZU(uz==YdfQ{cXTJ~TPa#B~P&})6qT@LW zsJsN=9_RjKnpi6JC+=tVr2XwORDvQA2THXo%ZKpL1}1x$LCvtzH~4Bx=1*$>bX-4H zKw}@eV7nP(c`Ud)`eJPhD>Be8GH;ke8+Cq2NijUq!$x6q@cw5b_j0HMZ;(`!&Ryxu zn%_g>iNh@-EYf%U0q}W)l4}YdZ(afpn8V+`lU4;^^o<@V!u!XGv=l~=H*`0w-%0BK4y ztdn6Hm!Xel=X9=+1|A){8?w$9Z3%t9k<|n=7f=wcI|8r+xJd6p_T5^;HwMKK zFCJcdk2byjvjQF5^}~QhrBY#opepWh^c{UFJaIvB+5^I5kSXX}274IJAxVQ>6-BYi z{GVQObT6HENQIg$7M=3m0`89O`gD?ETu>=Kgo_+75f?~gpA&vM`j zfFo%GG_6k$d&x+@T&_jLQ2{4k2FAY46I%xc`Nr73SF;(9hURXYV4njmO(;Ye{C@oQ ztKSp-NU-*Bfi@}2AtJ94qp>5m@rm~u4Vu>!h-*ZizHGarVkEp=!E81oz;kq!X7~^~ z83oho$q3gc{Mv}}l5u^-N5k%!_g`J2D!fgHPjJGbfy<+?GI#dDXzY{hy+#E>AXYLw zKj*B1Nz$T|A$|5@CuuVxC&jC&Naf&88v&&^AvrBmszaN8NTulu0~{k(j=(gC1Y9`Z zF&YcLy6t>z@ArOr>Taxa8#7B>@a#u^oJiw&gPYMr@J z*0H<<{7y2PJB3FFM;z}fPu%j}rCBwTPz?D~{jqL7R*}tY#62!#6fyeQ+aktE)NwyA z8M5U24fVC6V@$@SI-lTJ>6NUNw{%~R`Y}7v{$`3Vb93*Os3GnDqK?@2c&R&<6XM zp)6nHa0d7jP|*XyE7tsQjmEMh*v6)*t8iwfd;h`x$Oe_!3IxfUHxZrzYsxuv`Asr< z8*`xW`g00Fe&XLqO4MSr2%(fh{~k|-2`hnGR?9Y`>eE5|l}aq{YcRt&=qeG7s4^bwW{6=$XRy!{ z>l|(_f3VZ(U&Q)3JReJ_)`Ff#d{-cxvuI3zz_A!k1)}PdkxMSY4;|d6M;>PzN#ruMtQ3JuTRL zTkk$+XnEa8m(%1!VtGywWCp7E9obiS8()5*Vau)BWi6_{jv>>wjr#<>#2X2m>Au1pD;!jDO?+*FTdhh=oZAuqV178Q}Bj3D4 z-|XdH#X%R32eD#GBU~aCb;37~l-_2VFf$OS)FAMAxGPX1FuN#UdnV${WU4Q<_#1g< zGlY|vIeSlUEl&aPY56tFMYkliKnDf0pR#lxexZWxwZPg_-xU{qD5w z)dQ=bk`#s6uS|Zj-$Q-yYq|17X3}#@&ThwhG|JIwFEywX*J6QLQR#gD z3vI=}F8ZoxM*+F4Nc8R;EqJ@kLhQ2jb)PvRn8Yly9=;*r2LKn^z-Cht)5-v?BdG8u zA4ah_a2(lwH6?OxEpv`(j+?)w9n4cT^7m%K5zv;vhxb50&0N^}9>P(!=+8=J1rB%e5k#UUKBnfhXu&!(};vl)*Glzz2H5Bg3a z+a)1)veBAJgfqgUWF|w1VjqSS$bVvj{cr^8%PTPK^uagQjs+=m42;8lQt+9y*77F`HFOMy%p z$w-S%D5jdYfD`c|R@!Pe-zW-yzJ?Ov3#2M{ z&PghWBqij@VIz)JhAg4F_nZID6_e`=AcgP+5l-+prYa=NO_)EJ!%2K?LO+S7zefu5 zM_(BSDypQEg!0H~02Ih{JbE`)Dw^Pp_f{`cLX;x1-t6^m_>zkH{yiSM}v#Ry_ zBb%&vS8D%?j}Mb>;LUs9CyINH!}~Mr<(hd0Qt6Td<9tEq=~6fl0enaLu*BaJ+ZT-4kvoTknu@Jlc^}RE z67OaEvo~xgO6j|ILp8DB7A`SZ5MyfYY+<;WvSxSaUb1FPwB2w#6Ln)ArT(bs`H%Wc zZq1LB8qc^1KMC;%@8ux38fSNlqb7_v)CHgXb&g;!Tw#6D;IaKat=@4`BZo4QC948l*`#kTk`BIW( zhXeLF5}A0$F2phv5?;^3nGv;t(`0w>tdP`Mxe=#|Zl-p{($=6_kNK1nO2}J{6IQ7S zR{CjVI8qg;F#S<9I@?^re@A*%W4Ok@Zu(^jh2^%xThxhSs@^P=vRQoVrQxk;yBcuC z2G`@?Jj7tDpp9)GK17wozfNSp?#27qpj1#=B6kTO45vPDQiz)JI?WC zpO|Jaqn(6#>GbE%+pCG1^{8%=0)C;7%$i{Yc3BL=76dedh|PdnnfTN!$nOkQ3a@rY zEWl9|AlrW^VF3Dr&lV|1vVT(CgNd7$KYvq8 zgfs4^!TBDJ`PbkYrG7q%;%h%(whMf1g*6}e?t9_r!+1~>=F;~bmCNnJ(UQX*7g`+Q zuDg_zmkfV%Ogy`1GjC#e#c<+VfDE!Om5zK>jSzX?VUN+^A{wCB>@YB~w*T#|Z2Cmc z4O@dg=YQ(9LQCFzaPx^h%AVj(rTf)I((KF0XH<>fPa2C=M7*~Sa2*wdToA=b?pnXi znjFZI=dfghj)>4&ielCrnK@Cq;3C9S-$&qs!EOIo=4@qp2ZFDJ4_T`HlL zL#uF}g>L4jg%(pEZm;8KZRc{}bD+U zZDKL5()tQKXI`r(-)rTe5dzx5aY}&Zu*I#enF4P45WOm17$c8W088D zisf(X)Hc6VnG;&lA8xZDPl8;q#12@B5TuJ@0q&Ix_O_{AKQpSs!hygnH0%?N$UcP1 z4I2x0!WVbY*r0|o(ey#-<>#TFZpD_OUOk7g93~@O&^=HGQ~Uiy7KK#-bArgXmx9Ov zn(Y2Lm5+sUkJ8u|rGe{CP*tKoa}n4Y6q(w?QL4m^hPNbkq;bsDJSa_8{d;4IW{zR! zkoO_+mNmGI0wlL1_A8^niCC(GV@lp{cx+16?|vf=UWEzHf`gzvZKXui&3Kq~R4zV` zV4fL4fS~H7peoZaNVy9j+i8rda_}_I8xZHF=oti&yp|7B4Itauba_H1DEmGzR(Dbx z5tea)*CW;0s?)g`+K8`fj~~_;&e;t;S~5)BE%~wJr|@vo%+J2)^ygA>J?YSmc%xes zVK|&0^pl^q%&@NvrGJ<3RooGtxjT#wizWD05%7dpExRZP@Pf5|xr|0JGdmtR=g%Tada%AOtJFeAD zi_A-IAPLkL4lt^px> zbXN$~o;_E(aFq|wtni7ZV>k@octlCn%seda`!?{bkC~BfG%bwx`1U)(FOkED5DA!z z)6G*tH;hiAZ!+OS))~$QoaVdLs}|FlxsrBxy>~tCqvwx=Nbl&ElMQ0W;W&6Uy@x12 zYTS91%JHN9+DQX3SY}bSKhzxEB(n@nw)nz7xuMLnI6f`XqaM|TxrC%l>5Je0Z0hU(eyT=7r{l3NwqDaE_2`A*jb ztC&c%+kzM*aBa%#hH8a|c@9-S;5AUNQlSo0!7)48S=$hg<@h31op686bu~C4Oj9&c zRwJCeME=|REg6HH`tml!kBMu)U(tzYihsm}H0owm`w1a~j-B6_^eZxWz*j`Q@U37C zs29xp@#DASq_Z%pGZ~|I6;iU8XIRtinskd*L)#H|j8_^+0|_a{vCpPqP4?Uz!h`306oFlBioM zMD$B@jv;;X328NXP-^mXk6PxX?q}@;Y-o2!yc_rk$n3Km#+QG8P z<_;Uef@-72qDziJrs_T#T-EnpB|4Q050TFnBY9}x|H zvtl|9K0G`pvK1Q9dnbaN7V(1FK%p;A#S*}oCf$Hlx4xyHF*|4lfj zZP%PH6-|d4&pbT;?_sYvO{m;6$6lXO2^)IN@4N<`znI}!G?s6?q$8Ts(3 z|8W|;usvw(4_ncFTweP!Nofys%P^c@Kl=8$%3o-XN1+3DS39Z9S|xjc#=2|peTZ0% zf@p-m=-uYv4+y(cbu0e&i7)jKl9G!M>yAaWlA5ZN1RT*Lx zQ47#@D(oK@-3C+~vN{E6$wdLi{aaAGyA|zFEv%)g6%~gXorVJzg@cQ%%Y{DG3>V?f z$8ydf&V1V*QArd!VvUF;D*NY%!-yCtY(l&c<$i862u5SPWO;1my@EcH^(~HOIuREv zHMNelQi+uhDqO_Uh6)^h^^zhRP*t+#H-?QNH-g7t4>Ok_?*j`I=j*d3v8%L=K`(F# z+=AS$*bKZ5W^_wx-$0EYGy^Yv#ahD!Tu^Tu2i-FtT$k%}Os0DW-@}np`%k+jsKUsX zekEH9??DI!kU{j#T?ir+_iJWF*;4*vR(dQ`|GnA*>JGX5?n2B&EL>{r!`w^Vt+PsR zP&GYm zYe!LlT_;c=CyL9iiHhwOi%i%TM5PXv>dWi#JE?q(ETRwDKPdKCNA(O55{{7{s-JZ9 z4^^<*tHgRV-L?t3{U?)c3a>>Efh4FShnkbLmL~j z7bl(t8hbx(g<)Z@O%hKcEq+ONtP0=6TkN}{4o*@{hqb?sH~R;-NHi6gCkQVu5!6m{-SaK z+LOga$%*&A21k`t8aDa4HNdQbOJjaS5D!KS(7*kp)26Rj%CkE6I8X$ z>Mb%|f3-qur@ss_O94+GXi3DGkfECNen=wsOim2owQ-n6)hbwNQ+vvrsSGc|)+_uv zFH^Oy@SmkGki_4z+ObW8A8?1Ej{hv7@oPDp(j2htw}O!;;M1vptm7SBk2-)Day9!i z3+)QED>>XUUU$JRLifZp`7j!7blvkRJ<#u}_8OWyHU?B%yWBSR;&jTbLGlWmY9 zJ2u0Kpe>s(XjZM@^4@Ya#BJ#9PfdUOst=z49f z?`MB;eKy=p`T0^!A9TddRbGsz+v!_4cwtTC%-okbsE{MOqOz|J;zxPKAdd$G^PVGV-P!^My83pj+S~OOWtm@Yr>B>tWdj=gGCGqtJ({0}orcASBP=n^6_@CQ~7=nZ;n%6yhke1(q7b&bf z(X^OmOgS@GT!V@xBSY3KYOy?VM#9+P@ltl>^ul9joaC}hyR z3N_ag4PD^r4I8O$wnkcgAxu_!bpEdsE@c(^tgh-$`Hxa}^NL<3-tSx@WOtSAc4l0# z@>DYvGl(CAccm~pK@aP8A@9R*zgX|Ov~W6Ll?M@&oCoJw)^@OQ%8cV?+G4~s4=Z;) zj?F~!AjlV)$RqAw&16lQ44!s!vzfVpZp8DjbysYJnkdrcmbTv{Q{%-+_A;Rt zy94&+_D#L!cl-YLsNXX?rx%x52$IvP&4=R7aDP{Shqlpbefh)IyTIXThanF zjWU;P-#6>A4jr~uUUNfCQ^5|l^0tzVw|ak2*yGtB>JeA$1KrFE1yhY$zd6FuInY-u zk~{{*6;HD5us`P@;`T~9Mh;N$Aek!Eu9UwB?x=7Wm|Rz|5v;V;uXIq_%y`G~{PguB z?IA4g6dRx|df^0G1dbdEF3%0qRhSTyYT>EX>^l20#DX*-*V9AWiMC2P0h~2CXTKY| z90Nk}w4mz}O5sOym6-XYzkMBMnomrlo*tb26As8-xyY5=zbB@IqwD6tz$#}2EbG^Z zXBqQxuY*YMANW!OJ~oRs)>H8_p}W0#Sr9S5-qq*pf329Nsih`eS(#&I1|MaNJ&euo z4MYg_P)DF=cS& z$^*e!c9(N~lpOk*p8;0yVs{z7>V#l!`UD?#_xb(c)J!!U&S(*Hkw|(y2+h|ANBCGFP%Q`z~<>?Q~KBaq`kwgK5uu= z`3>{Fe+LfO<(9dU1mt)Wsz)#djrwks657d(PK2YQSZrinBvZYw|NZbDceE?zWt-U? z=k!9Ps2%-#9CJ6Z5!qg8Rs zq+3u8u@obQFw?=}{?ilLTYfL6YZ8Cr>3hb!g$o=M|ATNz&btww3r+d-rR+$f39D{? zgLzfqy$Gn&BJoA9&cA7Pv#3tyQFbpBVfzqSk4tT-L20@Dcr0Z4^YaasJF5!|uwTBt z0zzDcuGnF=WNxHhGX1yrRuP8oVh4Wll39<+13nfS5p(~t{r;kIhDpLKlGBdrnIkCf zjP(7C+n+vpo-ue^CgLbnP$5wOc@pJ{Mc4cj6tqkNG>Mr!EGQFooaXFiGIsDHSEc1b zrx3OwoI8-nAT`ZcZ}S-=(78Vv#Hvuw%n1fs(sLB}dy*3SE6>bnvZC`+v(D5sFBNa+ z9Yf;81|ow6iPlherivyNF$)pgi;xn9*oHjc zYsu(_HvX1WLJ>9=b3&}NE44aELLP;oh66+R+RalJNJi-?v7I@2GRTv9ORA9?zwx_x zF)wkx7f)g`Zlo)zopjq@1w0yH{M8$aG3CCN()L+|Jv|=<-AMJ^6q%(gC((o66I~S& zIqs(?&$&F_{v*tI5u1;*dP%!41{?l+*s zRO+APlCB^lWH1q=$$h<&KkQ#P;T}UBFi}>rgZ>sI7R#i$Zko_6DwkTW^mE@98er1N zAtoew`hfarH_g*Wzvq&!-_O^TeAp$PtviX95zMm<`2-LMbCuqH#La@U_^#OYD4)ju z{jXXuYp;y)J0odN0_|H=7*F3#aMtRJJoL7=97h`0u!V^KG|nP;it;I?!1LGF&fC1D z6d1MKp6LCi+hU49ZGj}>9HOF02=D4X&*oXDExY-@HCKnBJeg zI#4M1lNn!<;(gI;Ls7neq_3|OD|~$}47&3L@0q(O)shmKQ`ef4(7<%KM|(YP-BU8P z7zWleC(R1NvH6RTks&R6u2k>S)$qGVKjDR4VIQg$JXIrC?Flw)2ej2&WAYlM!Ng|L z#>vLnt)0w2T;l`ANtNrrTY#7*W9O1r$V9ZtvVpCe{bh>e_!4m*qkOHLa5&fa0!jI? zz$;yg$ma^uR@8L6JUfu%c=)~6c0+=5rOh#M*6$Ja(lyck9Nh!P#7VDP3lfdKkPiAh zJFHf0GGf45DO%iDPF}KwTzi*zWD0Q}&70jlBxDijbw}j3uf`#nrh%Qd zHyQV@%_4p0yfB_$)GpZ8D!n52I_W_8N9Y1!(eRwCqMZQ2`kdz?^b=vCyq$neFO-uV z`iU>7235hw-GBlwm1s~W7x)Y3pv)7Z$o2^=FUDSb5rU*)s_yuqkotRSPOz=b9HY?# z{Upt8guVM>2NHnf?)TGw3A>OJd$p}?8Kpv4hN{H(lzqy;j?r)m;T3F#SIdCoz_O5% zf9NId$Bq}2REb4IFQ*mc&a7>-%B7TDu$Z~z)s47Lzr0oIuNjC$|7ye;uOLKp&}g*l z;uGlR-e=>I3-1s(zwUr8TMnQ|tNh2QN8GXC`p|#V4!3D{jZnqvak#k-IyVv+QEIRY zM|s~qlJ8)VvVY%PC%7)HQiDS|5f~V$9qbX=uZ*Qoyi^`;SvR`LLYQP^aF=)7$Qs7j zEU!=5SA9>ous~%GYpa3$LZTzGqVV_Bo8exc9oman`FWG<oa{uFC8cwz2+8h(XH4o&G0v~sSJBh%j;N)zH(-J4kVu86J#DhxZZeo$9nq{H?K8E zD_Uzj&AIHQCNI3Z4p(7Pom9_56)`!LSlEYM$PIht5kF!jU!eafoLHc6@(_9Qb`C0L zHud_!IqR97K7YpE_tS*!uSmVz6MxKo&zRn}G_NzqPH4em@W6HS^Mv3fzMaYAX+l}S ze*s(ZaGfnMm}t5+WAsgWaC?H`&GqeVayiGag_|>P?KEbR;@?hr^QC(4r>4l}^uh~W zcxM)aoZN4r36I>W^gsWJJ2%M}iOAW4K7X?*GXCoon$Se3^v(wj?BS7sdzu7BAa2Tt zo|1>bNA>o4?<6ZZ%b1tAPbS8h(BN|>#G?8)bH2ZZ$;DVKeCoC{=bii(f|qU2cj$f? z@recs6T4Rz5gO#9m4&2VSefoU`|w+DAL86#>WD_)AZBqS$_^})V+#6}n1r-UqR>fe z<6|tpPf~XJX~lfnMvS+-79-jL}i(D!xfBcT4~Y zrj)!&!Ajp9dzgH;y#eQ@osyT>CpMSEedgOIlEp{#EUPg*Vydf>Z8C+>uk`&jjSW?U z)Uvef*0fyUju34};dl88Zkmpt4f{U>UR+yy&nQb0$#+8U_njsL?v0f_{pgO}bPAm^ zlCm0IdJ!51svtH;u?=3X^%*Ly!q%EHTW=>TJ#cG@#C)Kv4>asG$y{p*dIC)aLi(&^X#mQH71y^y|Y16@}U zvo~#@W2eW5e<#9y2`{~JPK3Np$)q#SNu^7+&kP?Rdp%u_oLBm;wr=%0TBOhDc_jCZ z%$CTWZ}Y8qHC3G)K-|k)n6JpwD!S4o47G&)(dW}b|3W_@6bT-3YWrnVtYosA`!JYv zr)0&_u(w6_EM;BAWyx5>)D~`XIuOv5sB_{4{?^-0TMiBPP_UAK8PXAd&Rc)gD zuh@)Wq~+HeRLhX}eG|EYBe?;1Whs@t%q$mk#q~HvXN6o#GxN`$=E7QQU$@(yCUEsH zkc=pBSY_X0Y(D{&X5w583z8@&OIrUg5%TAAUIAjz&!U7!A zB5}MVYd&>I{3J^2L&)7i$G{NVvh#F!J#xlKq6Wo=iGpJ((vXSZPFR3~Rz?LM*fgOZ zgJslRCTb?5mx3(O6}!nepPH322$#CQ)mG+1t$_Xb6pRF5 zBXh*-)Ju|C|J_8vyz?;ItU)?_$mMyK+QBcAr_>&Xdl2=#SVRtC&8>RPO|P-f@D>lm zF1ro}mTkl%))s5^0_9r6S&v#Haey?$3FeK4fPc_}r48{@rfxTai(iGhVF6jpqJpXc zSs-XWsrW7KiY^JhSc=NWtKcy3`=A(JgW9CNj1fnU`@|z=slNh@Ek7ZGd;C|5Y4I&^ zn+*7(P#=^@d)3^)lqNx+{3QckAZ)66ODd1@Ux(yaj5G7Vz7IWhR~8>T!9>ztk!5O@ z3xEP>*%#ZFabeWlu;`m1t8>Zg&~o85N%yn4W}_=gs=zu4f5|b-wAae>%`@8^viB>s ztzo}Xl5%QkS}RUhc05nEoAFEPoEycWmX=aQ(pb1H56wRBV6B_-sQo|j0n45drJIT_ zIrJVgBa8<%?ECr^Ramx4>4Pzf!~2Yk)y~2imQsMxzojh7qslxNC4%?p?m`0IOTxWh zz~54*Z~`<$OINJ+_5MCQq4Dyx?fm*$s(H*y;0@6usz#y9_Y!yOoGwb!8gEv^$|&VsDRH z-5qYNU1)pLp7OZbfs!=mMn}op_aCi=O!Hwx+D8y)Yhkv3#ZlP=RWQ;8f z7)NyQGh)x`I`u>ZQZJ%ASWNSBaGkg`lD|U9nFID%%|%}OqYu@lPV9U!4+``y9DH)J zq}{~FAKsaxcOgZt*fycR&wA~jeDFC^O)0;XE2sYZE2oEEJ?+%&dH zytoIkZ*EXp?I?i$Z=6TVkHjh}ZHHMijV`tZ2mdIhnM8L_P%Km{H}3%AY6ttHIE!1R zBZuE}zZm7?rdh&ayzHj*P8Xpu-{qpQ@ndPo_eMYKo zq8b=tXENBK>yk>>I(+pwi8ztO{rXjEQM-`6Ub-!)bp=l#ApzlD4PsNu-fFUGAkN*_ z0ozX3!kToM5a?pJ!#d;(MD2G@MCbcr=9k}6YgUv=MuSo6$r^ScgT@$3rF#5z=^V(I zd4~D2jp0?YDp@&kF`}+w`vY$cE~5Us=JvuGcAskIv{&k=?l~<*Q|U(eDril@tfLe& zUPF~Np)+n4fm=2*_$WGUCnGefdv;XFcbV<`?9-P`4|qMAT}}>5$1m{H?Wwn8uA;vy zTvi19I%}D0&+AOMvm_{dg$q|`s0hI0amNbX zr`F?^yZ^wK3|nTKWdG|a#_MDm>QNF8B62y1bRRcqG@uS=qP`F&zZQt7-vr0BTaF3aOYcsG|j~liSjg7W*uEt8A%xB|C;viGS6@h%hf4_24 z?0_srwRU5>bkLZ)H!)-PBWm z3<#c0BqEnO7y(U_C_;jO0)G;EE}izxNuI|ajR~)QS8W#Y-g%IAD%l0SVsORYZa{MH z`MM2ee1a?L_UGiIfaV;Bq#NXbFx5_FRIyn{!*wK-MAJ3?QOEUUrJPM=6kLkA=H@lS zb;|u}rw~cLZ8bB(91zsr^l&sJ6^xN=!b!$6hOC3h z2eYPl2i)TuI%~!3OBl0oo+@r^>8lngNher=1(wGXrw0QOaN5X%}7l#r9k;DF-x@dqGU#h7S$kIo zsDcAQV8aRzAE3V2<9>&wPe0J~CL?JMZDCeqm%bPDVdekPblyQtZEY9#3M919d!$Jh z=_N=H9i)SR6al4+gx&=qbX0n8A#@a^3yNS6k=_Ia453I9kX{r7zwJBo{c~rop)BaBs_!K+$-gwk_ z!=`19qpnfYgMGqUfktTa+7d+P^bz9zSGVS{x3eVd<#jVRJ#y=cxz0ty1f;*nU6(o0 z$x^aTV^zvx6`N&7grc>ui#yw3UB%1^<#(95pb7a9%W4_nnY@uK$DmOQ8;d7OPS*9j zW}m|Z0+g_?)7K%lAC2Ffdm5#_Av8knF-(?$0Eaz`fb$HlgLeF;^^VTu&A~gI^F!=u z{2H{_zHhaD@p{`6b|L6RY5CVS%@U67kCOrl9`zpe7V8TZHH@&;&D> z2r0d`^jUZ`9%E+~mrb=KL4gu?`UuCH0{06t{Sp(n?lE5PN9vfuz*`b4ELa@wf3-j} z<*l+LKg4dFlgAFpPjaWvp)om7BNnkSxeD>IXV69Gs|Tx(v4rOYCEfQj_Do7w|MlFT zReWqg}*@R9}b*3Dy>u__ZaZ?Sr zs@-T{en8YH{r+{PAawHjCXGp=#)Rn&NmbkB2jjH zD}ec-D!)u9uGpm-`I40o=!^<*dc^I0eP)G9BF$p#vXbh(Ap=i|cmToSz|f@j*{zS} zg;(eT$<|YQ9r-T%c$Kh;zG#64u*EZasB>{k2;@#G)He`=2Ns0)v$$u1FqGf&B>nfJ z$L&&*u1mw?;$C7xEn(^>W{hA-?u~O~>%;|zc7S!`fyG|eI%L8HpM|IvhiKv`yT1Xg zNeE^9Rfs$gTY=E&KrYcAJUD_ld+p_FR#l4%SGOd3-3?4K&8i-SPc4XaA_qZdQ`x<| z3^zIs<^rsMoK1945*vjkBRJq`r#q3JX-gwm(I2qE8zy8N<>OrG0O=qvF&1W!S%nNa z=@}PF-oI^pfBX?IjIAkp^}a!i-fQ;SM!p*oq6t)`LWtaBQ^q><<_it5u6}@Y2!m-$ zBexp-x3~UX74_^9HrV*TgYkdo{502_=qWz+K8zxdo^iwPE2KT8(T7JBi?Dl;pX+wo z+r1wxqVgSb$Uyw1Xs=F?@{I3T|14VOlm728>g*wZcKcHdK_Oo=QSYIb+ z4n54q!G~=i2DvlbPro+_k|{nEsB=&ktBb#my-gD4`kb67P8M$VSz1_!t{>lp{8!btAN z*6p=_K7_IpVewD*WxzTb%9BFZt(@|wJQRI!>FH`6y=VG4rEv&fQ5k}x-*DSV z<ueHRD#c8bZd(@ zAy3!C>k16~zWCbhZ8=U=@1W8ihtBrrNeYbo1x?YMoeBH+||G zoybZeaGvx%Gdm#qHjjg7mtW^;gAy_zEW2neanP8&e7A7z?JcQ+N)AvNqf#u zc1y7|!;R$<_;-;yN=oft@fa_&sa}?rVb~@e0Bw=yZ9DbBXl=eA4uG?6E;`<#_fO)G z2~2`K_i?U9(aWqu5+wwIBt;QFo(uOsP}qm0Z2ftk!uMcLZXHI*BrN*)RS9PH3Kn6@ ziR@a~&yW&)kl(!=CV6lT_JrcnG-<(L^ltVKTjU_QM>$SX$-QAJR)=gDdf;MmYBN~5 z(duiC-hd4nOuQbH=_Ec*@W6*;N$B*3n8&@cKz3C3lY4aFa=_Z}LSjF8t>>l)LI)p` zf%p{-#IwawpauU-xE$!u964yk~$g5*Rl6}1NVtMGDi0~PY@Si)3(CzO7qKV4w#4c zQ0=QO^qSKy%h3-Kiuf%m(O>M2?9;12YtQwWVBz3`1 z08~;554@i$QvI-9vUi-p;KY_bUlKeF4+oR>=_}=2t9h?iU^r^(r%?{s(i_V85xzZr z^2(pEm?4iEmn(S_P`@^W>I&vMZ;X6)A^lsdO=u6d}bce%`3e)Q?zH zT7?K-F=W{}*jsTlvcOC;*2eBRhBK4oMkD%_3xB-fUk_9WOg*6qqk+50D{eEBY_TtS z*tFQ1LU)Bx-lz^-)9^1LxY<+Mn+)?_Z8sT42p-2*@@S-@(pHMFaqb~A0_F-m20L7_ zL^3{tMIXQ)R)$P6y}Iu~e`=m5Sspi;L8kcOL8a~n=^QEF!mB}6Wk+&yKzA73y8cYx zE}2u`>teucCqbI0CYn7^DCl50Pudr)a4Lexz3RKCd;p1k+YU5gN6NWd?#4c(1|7&v z=GG&~$I9wuV*Y$B)LyOwHOKVGlXPypWGW;D$o*hJIN)tCn{q#bFsh6Az;W^%-`W& z4>@V1I-ofcfu$AgGRdXF*rb=~w;oFcQiM0sA@&o28WqAi4ahnJ^LpR zSaYpU`SvPPDOsfrJo1~L^EF4}Ym@`*{qeW6#MQsgHC1~OSKu9d7sT>7<(78Mx`>h0 z9A*F0{DEcqHT&~@#qz_mv*r?KFXIuKd!9PCX_UWSWZgmtu05i7y*FU<=zP~!kb~zF z7en9N2T)7oS`~!bSR%812%xnCnHQ*j`a`!oby9RblIf?yokCxYa;%mlKKbS1nsAYA z4xCwE^lIL|hfO)HyiRFETnlH&lb<`P84uqAvH!^*0?6;bWeJpYr&mlwuz3i$Jm=i2^AB3?;vbg}C|;nFJ*qg11fNy(TESl8GrI-g&<9E!?xhhduxGIh|4|2T|vc;~&^ zAd(>ydeZ_ykz}m;uS(^Ag{-ivlzNj~wa5-Jy-uVD{o`l8=p;6$pv|Yq6|ezo^Z{%9 zM*U*)6i-qCRxI}&vYp0JZ3L!PuyJS|`INxusX@Tx!x=!U222($S56`L{vMas%h#*- zu=vIHAaqeSQBMA;JzLz4u`T_4q)#E4irCA(bP%Kp?2EG^|D z*jlP^x9D*i2J>wkuSaUhx7}W4lTD}PGa+`h$(ZJbt8<7uIu2yT9Qsqt@-}h}KGm-E zslHFp->-{S^8)?-sgY^GRRJ9+_P_pO%-)03F0w~vEhRC_G2MR?fitY#55M+U+`r5G zo_%j5&>yp$I91;bI_@P}&8nhxy&T__7k`+_0tiHCt^zFL5Ep(NW``@6` zX(J9Pxa9dy^#56aD2ZGwXS)I_wdPac1kc?41q+z^hl@iu>%aZ5=ec}g;*MV8+$irV z9Mg|zInHhGwp-{sc|Lx{shJ%AK`N+je*PU}ZP6ev_2sHDc|DPhG4#H=z9bmR@eYi= zRO)sPL|#cm?Byf%sT*7u#|MxM|0M89d79(gSG!GH`NbL^{+s?pr5TZD>*)*43w?QI zJBEo$e}mrInPkYbk&Vw!r}pJEeKm>o{d7iNXRB381X+WljckLk-Xjxm06J3V z8zm}PY%Q;1ja9x+#8QqFVJFXo{WRd?;=F6bpEI}ia6 zx+1J1qS}QdQNf5+jR<9&1Ly7Inn6zq=@Aw`>9BX!>_ckwrhLpE(od6Xs-$)x^(cX@ zz{zECZ&Rrm@k{h;%;JBWENDg5{!`6Y0?V*$_YsLnMvY}fx9*2zENGNq9~YTQ2TyG( z(3sw7l_G(mb(5jB9~MoYdyKsWuHDoaAvnnHrqq~OJ59+>Kq^$U)*x`kK8FkfTzUrW z2AS-f%m_*YR4YTwdyb>xg1tx5j>W0hfT{Hlq!#HzZ*3DNT%D ztgAM;ML$Qc!Nf;0u~Ue1m0J~o4oI-LaAUMtSFETxx`*4=LNaFT1%XQw8YCzmJU_@W zbspn;*Ib18F>kanih2i2Ho(zY^qvFYmqElMsxzc^LYp>1o7(R_J$yhnL6Z00=H}*u zXk(^6cV*A;DGtN6%dum%ObF>%TS?W2I$&;<3|4-AVIFW?SGcNV4l{$2*7 zYhB%3sQHJ0hRjPpKri}4os`1ocKYhWLRx+<`hCd9d3jD?Al3PJy>=p5H58JMFc!5= zdcNRQgq6o~nZ>csn!7iD%)FNv>*`hncLD`1o-u9CsHPH;wvXoQZiD1KPTdSL9Il2_ zZ`41Ui{|Gdf`^%2IYcfLUI}@6MciNOxl>=@5Nrn@L~y9>z{QWj&DS{7LAOTeIM66g zlY!6mTe>Zg1F5oCU1M;>66uu&U%88X+h~?)b*Np}iA>|hgNfsNx^zMjcKh0cgE25z zClWA)K&LOLf_8Aa?Lp>u;j?s;jQ)UxJJn;D#N>kE=9=0B&y8zpv;2B@kw;`ht$55R z{AN7QG}jo+qIw7%^$h&ANJ?1#*@JuB4aAPTf)WY%-^?}I=6oRzpVQ4AGGa%YYhywOVZuf0hD z>UZ#Ru^i=gVZ30wlq#66tC6uTQ@Rxo0truXrDS!tTmk0%GcT7y2%px@Xn;3e7E zEuk^$7jZJ`YZc%koef!*K<&{UKvc*SUusQsU;M1vjqK>!9~qClG}ols>_kdf8eYm2 z*&j`=d|d<64{F>Ss55k-hv(SM7ZHhEf4@yJ5VPG-l;&M@AZ0jDjj1 zDi2?fg=(Pq_8_f(Ti;tYlQ;dmw*1hqP1j-ByWiZK9vF*LgtswiC%E6T>)ucE7PrAK|adn z=qB5i$s3MtI+U9cykRdU8I{Su*wdBlMI1n29XNYN)Lw%qXRb7YH0XhsaV3=6=4Y}; z5tbidt(soq)w~dNHel&?XdslGNYKbX0E@W!6KPhpVK5%WMf&?=CY<*@$U<`#r`Cff ze2QuYZTQ&B_#x}Zx60~P!_z?ii{-37i^0QhnyI0ye&u`>j0g7_p%v_i9%fJnjk8omSdx_D5s*TI_Et#Fp z=Mw$kvh+bilPqbs6G*I*4x?m02bj>I)ns=$(7}mBIa;09w5Uh0#)9MxI3MFD!t`p; z=V6Y{=2-qU@_+AHDEj-EA=s#;zoC^v^gTw$&eq@hF+ND`#3|N8-X3Jfs0Z7aLd%3! zAhU^lc1({Gs2uT5)cjL1zR0O`+0srCEmpNTsv@!I#}kb)rp8rdK%V(R9Z({=o%82C zF#$uur^fPla(|06C#*D=ajJ`Cu)(M+euc9>@nph|wC7U?PP-RmN};EbXPaAFV-XRK z8(ywE0s;Ig;{eWnyl(|0o9WqCL8n9|E$t6)?5kW+@jjkn4&tPIx}RKB;u86{15fgJ zyc3uISV5`&Jr~XTYc{E}Ng{m}#Eet&NooJKFUgCOpE_gXh49QE*#{sQN(w*p+T})6 zzlrv23L^=OFMeaG0@S|q7A@^|o8YHK#SZV3hMmgu5V^qUpJRID^GIr5Q@%k#czm1k z&I`lEdm(7&^JT_1t7l54yOdJzzOIo4I)1C_$!o~1zEVokA|-rfg{+cJDat}AD%FLe zrH5Ey8ZvQhmgh|kL7}P5TB(#DSzN7Am&=9{pAPfU>YN1kA_f*P#NVC+%VI`cx0j);ZXY4zazKN(g#kb|s}AzBAbADi#|q%3LqXnukjhSr{z z<;~pt&TtoZt>scs7(8sDeOzx#h~GS}eynma#h=ks@!ZXqu&5jMl0bj`b%Tg&bKkqi z*Q9d?;hCrB18@M_b{ci!$|Eo}W>YTyq$zTAVQ4UP$hhqhHQqI^f>0NnXu=2%K;xei zs@6=%MTd%N{nR_IGSx%AkTonb#cw~8iAL9$wGif5qPRUkhZMv2Bx^)#B%QO za)(=ff-}@Fo_F4zitP<4CDW`D?1)~0c9pF_UI{G0vQ6%fA2N->-Vn6fh*9(v1|}d` z$`DS^ingV(Xs_U_2F8HT(X`&OzN(jTDumHM#}?VeKd7K-LcZ1@AogYDPsL!8v{k`- zcxFA9?NKKZP(?=xvet)?5{Y2+Gp@hEUJdRl{(X!0HnOrWxJaSoqh;#BK2vX%S8aF* zi!2y^RT`?5N8+?{KW%=r_$akFOiTgwWIE~^?SF)WFh)p zf|TjAT@9Iwe>nDxt-zMM1gnw5G?%nfTucLa*=pQY54W@d|H+i|L_(I*FB*0=^n0n3 zPb|_)RCDOpa-D>2nm!#G(;z|mn1`B=lQl0ALC#8AuEBO{hk4#KjN!tS_al68AcMd>HDz9oX`SGx2u zQj7EbTf<)VKb@Wfn&%E#lacHpB(LwGAGF|Buj35HwiB#_>X-W`3l0oCz)y)XB$1Sk)}=uK0YK9JTpf?#Gw& z_qS=Yj{g1b|EM02z4Bw%>BC4Me#sJE39UaOk@{8H1qwaLYx|Kh5(yrAcP50+ZYWB7 zCSk9ZXPMk&c&y8YzAjw7%fKhoKM@@%8;lN^-5_6Mp+iT~;X{fL2@zbLNwU^^;Q9uR zhGEz}6LvLu{qvDuU$w|(y#Yho0I1}Uua)61cpBu$pW>GO9FN)-!>`@6$}sir>z0nE z46THKXs8x>w!q`PRhCfnBQ+Qn<|YfVoc%H2dfYD0b$;slr)qw~Pb1yF z8}B}i9N&fx3Mst@6=r><#m&%jX{m9QLP1Ws(!(pzqw|uDZn zmA53^`e6r&qxbekz%FrKs!$V#es-cmoxU~e>8~*egynnhaMo@ZGP;t6Z-9y*Z8nnH zgBXOBGBsc4`NC_(@D4+y-g-hR)CW`~7wU#Nz?gZWuTC{X6`G!qE^J(#wscM|8ca|c z#ADiXxS&_%HKXpZc=>)P_Nc04O#A!1NMieK*7JTL2S{JgK3UKJ?CKWwOY8j01a*I^ z56)Wa8arfbBH#H*l1$-qp?Mi*MrOBcI;LN0+Hxl?z)-vu{u>*-O7v(n6&ufpx z;%kf3qX01Oo{&jXRv@Yalk`*?dO6%N72_9NhaO6$^lfh7<(L?@BB8IJEtt}t%Vocb zR?9pehIc|I7m2`${^s^DTz?q`rj`tZpuj2QQ9p+?A8?d!2BRg_tNoL#gVB2TjSnEt zI&furLDjbqZUgXM7ZbsB?j1!^wVWtv&{gp>IikEHI-kCJzLmp7A~{VSJh6|GBDidE z@C}+S%N|y=N7V-GBYY(h=}U6^8E|+(dCrE-Du&U=U@D*ve<=Ga%1Xp+$8jOuArZky z+pnPM?3Q|(fMtteHz5d}k>B`WsY|1lBG{T3eL`=Gd?rs?TslMB3f_qSu0FmOK=N^- z2AKvAA8i-PFMISasJH?nAC0LbD>${s4Ke0%fM^i#7R}ys*rJxMoIPV*NV=Jx_uuc= zk~?;Jhho&LuM9rq+|KCAT8h3?cbDOdhI?_5h@t|_V{f}5=$55`yWg)VUKrWSAAWN+{JVBzlDE75GZOuNIJ9nPD<7Z1QGc=2bMSdf*ytZldVB28 zH_5kRD(@Qq9I=f~uP(g$z`j1XYiU`0FY5PsmR~n`v#4FgF2P^=Oz$&wJ~+6Wt^2Ev z;kb_J*!nz*fE0Do37Cj<{z6dIHu+l$NV|0;DDih@IX+vn zJ8xCO&s95nY&b9Eei@YzcJYgb(7*`E)i&txzoYt@pwQ1lVm8ULY+4+#_8}RX)F7<)ps zy>~@mFO!Z`AB^C4ku*_lI?WH?N+oNn&d^Q50gmiE)e z28=$cr2E!%6|dPlTTC+WUjA!fD~~CEKmBRAHwA--doITiIgN1iz%72NyguR9k2h=3 z;F(ILYx$riM0?YC{*g>@uH;jlO@{-s`zc?p$`$7%gKJl-u9*nSxGDZZzpsJfvOh+6 zFn`!1y>x%k*q=$Me)85kMVb}WK6VV?vW#;|M9DE+VrxN8%+3; zap)Th)@nfS%wT}UZJwyt#h-K`!Epe(270+lrub{qArFr_aMn#wB(ye&C+OZT zOgW_p%T$2vw9Q04JHTUN^!qPa-f;%s*@Z=41>x`4%4>EHmAyMw-&fG88az>-fyt34 zP@Jq5ZQ-&^q=_8O%$WunDDL#D`qL1_lv6_wESyWWRA;tNUZl^T(u-&qU zxRHEJG7kyGA#}YmyejNqwCQtE_e{p?@r-Uv$>F=-hf2Q>ACtJoy{lrTHXLC#JZ3K3 z`OFzE`E^XpbM%CH7#8N5ozyqV@SK`qI7v#%Sp1ov%eX@9=?~=Y(UYL05tH2&---V4 zrHzIl--aheLbCCl;Z(Q}HIBQ)=b|H@FVm*6OG1BHlvwwlC+|K_`PJtTI(rQsB^D*w zZ7bbiE&YT6FfA$9ZSMPLevQ{FCcc?q_&Xj@ti__&8n#nt`LL7R*KAd$nysfYfY-=^ zHa#Z0ZJbQn6DXJ!Yv`tc6}lUJ|8C3Ip8r8r-kr$K1xdM>g~tI445O|~CG~9))7zbO zXHmx%5g1^oyfeB~0s0zXUT^op%frDRH2_=~^t0?J9Ozd4HC$|kn#vyj3-+xZeLWui zb{dQ5kEs2`SpIl4zX~XkEx~A2vEjL1>wIHI`e36S!IeZiU-ah1TFK`##P&s%5jX^3 zZEmjxRO=lwl+qJ4y1^Vt^|ta``@Lk8y!et>IPmZiyv3yG9FKGeI{NevUw zOEjDPF^ikt1Y~DP%;K&aRX<7vI^(=p=+ki_k3rZ0m?x)@dk_(MO=_Ey1=uW|y;OW<0Ae0(k_oe(VydUQ0{0G$*TN?3%&O8@d=eG?t6{I_18 zGTX7hjn$PBKnngOcTf%2K62ydQDh{53|+PMN>yLiB?jr9w*FMA%%@2HKW<%WHf3jc zF0pi40+;IgK8qc40RHmx+h|)T`EsYb+0iXNiC^0E?t8cVylBd~l-(tP#7QrL=-&YW z^wO)H_|CtRpwOcWR4=D>R;uxJu)*0JV(;SJ zu@ggYCpx1yVst16IZSFG0*wlpQ<$AmqpL`;he*oEJ&X1pTzKI^%6SOkWI#3GR$!AC zTSmL;?s^-?yZ`a*JCR2?EaNd#G5Q71E>l`I5;zsl?c_Ufyx=z69K(qH)gq{wtlw7E z@3KgP3rXTdDoiAzYMcbX8hEOfq74;>%pPa2DnmisDJbMhX<422ug2W^tK^ulwsl7iPJP$te7C0bEG_WsX zeG7CH72e5Za!Ed2njWD1O|aGvKh54whD*kPlfde#fvoo+*BusxEV3#*(~Bqcs{&Ft zcDHSut@z%!qQn703-BY}`?q?3_rU5?xjFlb&2L?=&>!Lh%K0FSiAVJ6oStSRWg~Ip zU1_iX{aNIMiajP2g&a^BSr%*?~qERhvSe0c|i1-m=L6esSgpQOgm9 zP0>sQ%uNP^MVP#b7JQFqGV085g4)oyxmx`s8MuScaC`GPG&zdK7AepXjfeq11o8la z!tk3jaB&bgzh`?GGbFvyFT&>1nOCDxIkZ0aa#+mO(Ws4Q$Sxjh67szOXEXDi9EUcqcH9;!9PyFnw26X>>pnH0(gO&)dwuFTNkAhRb{JY|;W{4m_vAZx<{DDMdI$bodkOf0#v5>W_+>!0SsSgCg*n2T|^B89xT{XXQ7`HXz@ z$X6yfw9DILf3Y_g^<@P%dWu5S|!D+77#85XGe?`E- zDXJ>IJ8~i~cS!leMBW3eQ|K66U%I=qHOTSsGj(_8yOWW<$lv<%QTOxTAs>QK(_<|D zuaF4x*965vRXVr6<~d)!LH)N^Bas(FL?*G3=f5g;hQAbo0Fw0jSPqNx5JR=HR<$ny}Eu39!A9yWg@`_ZKVzKbBJ=7B-p zncbeLPgOjtdU_XWci(s)l4t$U=g}$V)y$@8ZVdmwxSh*}FK}BYv@F}viMskkfvzZ- zw`#2?ZnIv*J?5Rbrl&;*!&WjGcTU95MYc1(DRl}-hn!c-zpdk!{t|q(ZpS!7VQO)% z-Y@Q9lnQMP*F-KW!UPIp9YV*+*goMt5Eoiih{=AC!>>VFe6=fFF)I_3&v>)-V{MwG zSgjsn_T3<@%YD?>OiZCItBcU!c%6{CorU;^en!RTcX4Yst@-9X2g$P)cs@y0NKOSL z)pjDCbcp3oq$D~L*~C0%g>I~qg}oePTo#c$M{FrY(QWhG30EuH4I1tvP&YLwtv{%w ze29;tKiN>)hkV|L1c3sSSvrs)Chk=XMGsVi+D2(lUCdMZ03hGA-pHKI-);{4!{{ajDOH-vi~YB2dz!PbVr19c>-4hpSM9K zJ%2wu+_D_{3alWH?;Ec`K6VM7LX^IyN(US4L7o+1li0$`u2b9ur$aNLda4umRTb)z zi02ALLwbk-q82T<4z8FY7TD7yayK58e<5q~gN993bIV^uvd_RomtDF7Iv^sw%$M(5 z4=jf3N;>siE~H}Vq}N=dX_B4Z9E{T5!wT$%+wA;iT_E4*t@M#>)FwDYUfx)TCE>ZM zaXrm;ArB!yJY$(!hs+8Lmk22;ISj&**e5h)5IP>-UEVwXOWRk1pCl`_3wi88@<_L1 z{o4ie)X9AatlorCG3h^l-?Xte ziKCq+a~g~9;3||qiAwo@7GUxPE|Ki7+`lU|u_tKu3C9KEUol-ibIgoh+}&eLRIk`K zUCj=7k21Uu%O3QI*xuZ<7v3xqZ|3`h7tm-#=qq7AUq(GsS4xJ4)%l?d1!u%|E~Hx_ zCM*3zZxz$U9gWeSlenCEQj%52V-9pv+*luj?GGfhkHa};Z(9z|8v&1C(d+zD;W!(K zM05U=9bGFZ^Q_@u!z<@uX}foM`CkZHhFmw>(Y$XWe!%jxqS5yZ@W z3hL{`*a@b2KezEzxTjAH^p`6e3;OLLUr|<8nYc1QqaPKC*vOdS6 zs)tcu8mtFfk4y;1>8;bfE4^L6l$=S79Q7LL&7ymGZ))350AXVVsOZ2 z4)$_4UiT+BOom(y)GG@(a>vW!Ytfgvq_c68fQVqZ*{D6xwUzm;+^ftrc*465A;?#EoM>1coH2LP` zdtK0(2TCBpVEPPEZH6WbL7Tiouvo}zl7#(pSPHO59A;kQFCov&-b4=sqotoEB7e~H zkHD5Is}m8`0{^mAv||5tASZZ0R|qG$3e6W}qwNupg-lZf>*JQWUjQiQjhs(cjQoSb zID?5(r{t%`PYMyBHD*iK4?WIry~f=`ygr&NJMj>Bb0fZH_Zv(>D*@ZfW&Cf;4z^y9 z;7_)wJqlrE8#cAW<>)efanrsH##$V+2Tc3>=$0<}))->tPuj*I3>=ubt+(IOLD;<0;$Q*MK;yT7KD#~C;{p3X9TSjDd z82app%M6sYT3?yK=`L*FpIEV7YEhQZDZu(U(Me8gv#JHM9*lxuqK%xKYyyGHg~S#7 z{}Cf~A{H~j%h}Vk(GNDp+#uU|sx0~OZ5mPbYcA2=3?iU6%EviV01{AuEvL*$hRh(x zyv@Hc9Pb+dd#_w7aTV)IMYZt`htw@$zQC%3>%&d=@TK+ck==YK<28uS&*sRfS5+r}E6uA*P zTRW7%`se+-RY;>sTrlH*fjMK~ST7Ho?kY00+`BlmuRI)+{C(tV3X`4t@OWb_paVV4 z+PGqV##`@T;F8Ivkw^FNx+MtRfJmcLPZO?jK7(;%Bsvr0nJlpfp@4J15bOPfbK@0j|nm)~-P=SwLza^TV&9C~j^Jqsk8GfgP$CTt@$v&_h!v5~k$seh15*;zhcm2g4r#e^Ca!WGkS zE7y)f+MLq9Lc3qc2pezB)q8V=^sK-@!g2MT9KGiUt)NuJ527uw*^K33x9cD&Q^UA_ zPpva-iS@pPxD|hMw0y~Uuw^4X4YNP0q(!sB2!Mf#F3C9xA4F#V>tfg>ckD490kwIa z9IrKBG_J%6smDyAIRC2>1yG;G=;fp436bI7?YQPU%L#a(*DCKr9L<2>WBiE&0Cynh zxHPYzx$l()SM039FE-Bmg0en`Cqx3KqyK)y6rpqMC(Mnt2fzYvNi3!*$7QULA1DlT z7pxhYn0f6fAxk1(Yc%l1fF07Yrb|<65Ay2aIktz<>EP8=z|TzcC-o0kdyBMb69|ho zGlFG9uw~gFa7l#KPG3zhx`k&@7hNdM9e@Em2U~cn9w>{rn@PMhbSI%05r)>)LHi>@ z&|?&+JtI)0ya9{m6g<;(jx!b@f$Ngj7Ga^%C*ThFaXxjMvbu9I!(U(A4E(vM=bS75 zyqFJJuX=n1Ubt7^vRYh{9-P=y;ZBujb=@+RpFV~SMWdjpU~*nl&o#nv1bm70a{z1u z`SgyNY!kZG`nCYay)`0V_SI@4)(%6mC9P=AWgokrsL>uZOi01+zt_~ zToQGSp-XtLKt{}WQ~J@fc+jde{G>yh+*|+UQ@t?b{21{uj%^!-DFrWm>I2yc9}`hM zn=<-K?n+2bhmxBCTV}NJaKuHWU~rtIgk0K7aVv|1Xb>2}T2> zB7hV%k59%0)So410;zETcA!u!x?AK=tPTP_J2?oqJu>>D=g|tBkIv2xOQ4rO zKHK@B?F^04O8N{xzfDL$P6xs8y3+fb=}P-t^JpMVZKCJh*mGWWiv4!D($l z#dWiuqu@cp72Hm`5@?&y?}5J@OaP(HsXt`J#S@GzqtHQxu*eK_l55)=#VGl(-v50a zvmN_Wg%Fcbc31jlM8^`UdVu|Wp#Lk?g4q;Gf@%;>7JA{3A!WW!YU!*sBnb=)0zGQ4 z%RgyZA_cScq>e8qU?}SS6Ge0ZSw+~=!bT;5}mv0Lp#ewwS_PB4O@x93>P#< zTXf)l31tR8iiRVvH&1aG@Co)C*08tfehcXkyvl3)jqfCP69!Q46lck20{JSAS<-5bqow;>I1!Kz zs&LCtv_%K9A4;blt%ELy2&r1_bhvCXJpKS$dlvbyAlKEB*fna0`~llUXfi(7bB4+0 z9;XA=qa|_ePo7dKhsk7wcbbT#Kd9$q0;Qi`4JKo7=`eI6yV{~YMj|*7ul8j0LFDku z$k@d9Uu;zk^q0N}G9)pHQZu|fUVSxWFmcNqEfqod$5a(4g}jhN&KbRf;qRbhwg@tuCjP=Qenr=~}SKT>cCg70tT zvGAf&i|+pIHJzB@)qqwVC*p*FaAcz{V2OdpWort6ym&2tjMN&ty^?f`zxS1Q$UuHg zFYjwv#S?HRHq`O>d%79)10Dix{^0X!M>FY z{bDbGS~>VYn!vR}M55l4%BX*#??*Bt@ICr-^_4vS!Zm_}*ecoV#XpEP9_ZfE-*q3 zU(U<6Pq{HX>@yj#$5MCNtTrPSYo1ya5f!+^K>HaS0A&7!qrTiVUa2%pTG{9V(-_rh)@VSrQYp+ zSXEuf*JhQ)gTG12r2idhOq1ydQW#l=CSd0n6t_+}N-(U`bU7~LG(#K5&0k#bdBblMIS_5ZCxOH zd>`Df5+%KH-QX9k_{K;lvhX8bUsB>piy&%Gl4LKV4|!0JfTpR}ulMZtgp(if=| z-LcrqWA!&j|4KK<85wldc9`m!170x(2|u3(5^Y?+!={AO69`VxmDl{(E6D;zfG_qHQsH=~i#+Q3^aD=Mfi)FpZ_;Cd z(eKgO(rN{uTxl;x#2O0WY3Y}Cek4(m6N3oup{)yme)CmjYI8za$i3ziL>X7(K+MZ& z3Yn$k2KeYp7Es4=A9VW5-@gvykXeHe0a?->12j6M# zV5taVpsr~I?*H%v=q7-@jR+}Yu|*QVUEtX3i0bdK>>qUKuNwFF=7j1D0PXtly8N%~ zWpX_sH&KRFen&n_^s5O_v;@dUX&l5}k}=75ig-iSkbNo)J;Prs3F)}WFgOmee#U68o0Q5#CgfYzIn47T1QuDV3M_|ziWU1>8kMWKAZuv0&zr5_erl}9Kz zPLYsF7{UTs)h`E>l0B`#eU9Z?skZRXJcN9T$5{C5*CIF5^Y)&J%d}ffiM4Xdd`d+K zviF$KauptAD9K@H_~SCq7g_ADt#0F_V*i=mBixUA#>H;5N7ISRiPs;0@@^wupq&?y z=!a{Bcr^qi?Z%$CRY*FpG{q}@zVr&*8@eYPzvtt%7jX!lLQMMy%6DDN$X}l8mZwUH zYC=Kb>q%P565wO1^M7)^b)rESc^=V{zwU~lhDkW!^_J)V zwl4wzBqz}$5=A?zbt0ALJ39b=(t2;Anp3j|Vty5n;|^TXQ!6Q*@eyVJ!=gT( znk|Nrv{Qo+(1PortKTpiJ0fi?N`6N=>qOAZtx9sX#H{TjUNW6G{WJjtFeI0P202~? zUMZmw(J;8)=1c;&E%gw1vUW_sq^SqnhCDx&laH5oc^n-v5qx!wW@SMrj5xg8Q*ByZ z`*xPu{>9Ww(DR>AE2(dbqpGu))<0R4NTZfshom5fZnxipTRYA*>ckCnmQ`=)UtkrQ z>|5e4P|tQ<_2?of7D(nNq#)lty4~O>$!#m1*$V2~xKGW%F>lp;VOwh9PhsP$T7XPIvukR}943Zr ze^TW&_sOIaiWl!9m&lhZ-6VY0L5Un$TR-NiMm)HVn*d7Bea(*I@XXUwK!$Ygfh#(% z!AwEgkIO5mD+Ju1?5hIW(^kFeiIx)60dp6s{wwB{-cC4UF#6S0V`dQCKYv#B}aL8PyTB~zefkA zDTo4kg;#{r?q?dF*}c zY_fIi9DDEVJ&!GWr8w3>86A5c6cUPxkPgb;vO>xp$(DrQ{rO(k?_d6Ld7kIKU+?#8 zl^V;dCy|6J1q{Jon@BYq$<+Rh1@Z}>r+>d#++Y=#>5^BkQFmu4RPQC5LKNt#<9R1R zz9>T{KJw4RdN~{UVypyBCQ+(d%q%RxxRoG3T2e=MQl^OfD!iQwtCUN`Z;h)d72tu( z4Ckt}63X-&(^{lO|tJ4FF?5V86q z={K}CN#>A&`QyL6mlx)DkGlTYf#{GnQ3J2WIeKkzp}ZpatWWx}3!lEPM)Z5OaZDiU z$vb2N-zTM}IdXxXKznhL+>Jz_P!p1Nu;l+l8;<%q#e!${>4wf5)*3u%0^|_M05KH1 zs36pGa{c;@ZSMiwM(g|QS{f=hA>~`Mw;qqY4)Q6LQ9A$0t#p76n&Y}j2`Z-K8&kS8_L!+j}Y@D@jzns!~sdV%+!pNs081V6r9E}S*o zDjoTqJ5_cUZb{AV*$koNoOOg^1ary|?~F#EIo$C(b>6S0D7fW~y)EH?pSL5hy(!e6 z>?*TF$6U`)Lo{|3AA-EI8i=`)uOe;A*aTIf=Ecpbs%eWYMrIBoI(rRoA+>jK*q#9U zXjdAL?6-c2-L7*aKknU6cFf`;N7v)EjkcR>ozo<OIAbH zX)0S=lvnKm2sU)XKBGrW&V5zw)^ylvIO?qo&=8*m4DVncgH>3*1 z{WF$W>U8ZrXWR75(b54Ktr6Fm?Knmd4*~LUlkgS&jf{aE-76ebB-iq#DNdsu0ir^QXp)311$Nb0M|IS$uW|AXl*=jft;@oMG&r4L*2w8 zTwX{^u(XWqbctg^eQ8H%;ybtN{YkL#?-(8aBtzCCC@Unp2MY(VK03 zPzm;i;cd~yrgCkvJr1}Q4GNLTI2AxZT`!RI8^Q#rAjPw04 zY$){`e@Z&0uuJXgs|`wD?t`P`FAze3-yenhVthk4qjYiQ_W1!!eh4~IoYSL5`ZLzS zA&XHEj*Iumc6;@OWFm9(3vb(kuug(Gb^Mu^@3K%%zT>(ig+#2eqQ4lQ{X}eSj4=x1 zD3V}kW*K)9+Noe`?SU%eph}wMYSMmDy?epR)!QdR|lqWB+#10ZH7!$}a1OVeC=- z-faJD^~J*ZSDix9QI?c!y8nJ;{tO?DORUV6NWGUjK_oql?R-(NPjT?$){*twhgF$< zU8`$>h1ZTMo8L>})kRPmlntQx+7WmFGze86Kq!d*w#TY6cEdoKkktL^HK>HeShcBH zAko^wvc!itTY3@I)U+a))Fy4mP#|hnSP(cai?IXbQ;1CJ9_?}e7V>tjFtf)SE#y69 zoT2?<{zjV23h8J>4$RdGH(3U`BRIjKPZ9Ik#?=+^C!3qd@L*wCph_LWt8Tc}a#X77 zm%)&NzE3_p2)#hxqklQzr|;L%E}ZtoZjl8xzMC9pvi3Vw2Yj^ z%?#ab=Xpe{d{1)yxi8j_M^UCkeC+{iP9Tbud5L2@%KkCbfUNDgb+9k?cxb9D1)g^| zAs!vGfqw$=sAj4@fy`C%&!W^$^wU~8B$-O6-fIHJRzp4?E;G=M5P)l)8lR2%Xmrj< z4_UhwWx#e_K}ymw$9T-Mt%pJRYTi&UYT)$nVpXA#N)0u1Jr?sHlm6tc=IPfsBU^R= zxhNjvk<6XbUc6_{FSoqct-+)aveT9~DY7}RlDPjT-8 z2cI54=ckc7ZLSJfP0a6r&##)%k(GCi#Stk=<#1M4&bdd&#V-gSEkpfEV3?VzXYyNL z{sr&<211&_LCanuH z4iiR=0rI#&7?<@*6=ib-8C>CFz01ywO7I8FSYt+hd}Aqco-Xt!vbzI505#`HoUdt` zaXrbU9LKs-_?0jUr=;FJB%knlb~Ar$48}d$HNwcn@lxM=)vBKZdYz-?5w%MOvNO{AakUb5IC+HIcQ4e9nSM^fJmVWa(auZohd(ZO;&{n(uGUA{q4N;zgL(fVXbFEx zYmel_g|>y3u&SZ;!CRkC*oR?h*=$o_M_LsHUNdIt7HvPuQNjc``1H1eP)Y*uO?l*$ zK!JL;)9yl9B0M}Hx3^5ZrEUdkPgRL9U|p@}$cv?Rq0Ch;g!OE4_|wo`z|Aho6~mPn z6qL6qt*vs&6t#SarF}B0@WmCi>=4_~z$haqOxX3btOWjtK$JoKJWS_@UZb}0Y(9sp zB(`+Lwwjh1duv8y0O-o`i ziLbN5!&KpIoT|nEh4h`!3H7isiSD;gnY^P+m_CU zQXRmw?-cfCtgMJlSv3CtwE!?RH<|o#;!_SG20AYzEu08&yhotwUxk!?;a;6j*H4*H z6?#GbhKuPKZ>#wD@!8l>dQ2bms_hU`cxr#$vV+U-PN!u}?kEekQ+N&T&5g{YL|`a) zSX_AE*J%4N=5N48cTW26j=v)jsJ{!2{m+5k#Wpki*UHs}XjrCD^uck+frGEvXUz{j z%BboT(yhx+@L{OA$$^IbtEJ}WogRd(xcQ#92G}^Cj}m%h+wC}&59}|$WpgNu2l^K- zIUbY{O%QzRGrT6|bh#&{;YV5Ml0oI*K~7IS$Lm4hvI-$x;X29TpbNPYoRl*~Q%8YV zVqyJcDNDRxQ#kKR{H{2C;`mmRca2|Eq$(X+9^EOs5i4a`z&Mir4eNc}wg&wvs~Kj# z23d0jD$VfZ)3!35y4M22;m6c9YGfFjX#^Lqnk!InvNP_GMJeeM4_sn5S#l;*#)ap} znT6Q-9s0GC>0M~ZhDoORj1SdRv<@K^1x-aTd^eB;Tur; z<%-HN(SjGpsm!hf##?lG{Ye8YN7C9?w+a~5SpN{-Z{kOG;vM{OmSae{#IC(le)_?| z>=|sxfGFDv4=N>Y>1#8*afwZ|LS!U_9u5pX=^s>6b;k)^gKVa?Eh9Y8*t~@UO8kd@ zVj6|Wv}2AvNIMv7q_%)Kwx3SV45Wne)=H(;2B9oDE2jF(B&7C1Bx?r!m$56x0yc&C zXV{#=acsR_&@Ow4wm^i7hApBnj3Qx6wF*rbmLW?LDPV97&LjO9@wtYuN5j4xPlB>3+Y z*o#)rT7vxxct)Qu2=I68Je@1BT0vfdaHl+2bdo{JN7J^m@ThHZQAQN0qNgs@mgMfP z;@xFi#*opcE$--3Sm-dh>Z?sIQQBhf`2Go|TF8rX(<#L}ri5r(?!ldcn}Ae>`Dhpa z4rO68wc4mV6P3sYmq0<6V6KOi-^%rpyxMWoQvyGrT*e@=S?xp8 z-`I&X{w&BiU2q!75{DH^-38zkf5|kf*A5e8ImCiBfM!3m__fXn%tulx<|!y&b{F2Q`t_!q~IV#I__VE4~|b z3r)UaL@wwu?CNY6rPRC`0#*B=)9{{{ZcbTSV( zX?hWBYA7$^?*oigo6w)<c7DD}Fkc##=w*Vh$G|x@G(#*nX0Am>U7<3ij`l4glxTbWBnNPh?V?uIC{9nXf>k+qd`^^Ff24Oe`tP`?S@4%MK!n}!kk>Slah5K}c*XDSuyCstbfhWTxfr8nV1w7oO8?i$puX9ODOYdO*s zM=9M5A=UeV+Kw5wY(>0ZVIMxf8xt+nOt&%UX^ny#y$Ej@iN6-4ZtXpJTi2cQSo9#U{-%}=P)!4KY zlQ|)Q^t*^Ntsk7h$WO4TaMWv12UuU0MMs7lAxvP;<8OmH78e38oeeorhKE$ zr=BTiSsP09z{IQ&rf1Hp4w!~nAOfxe{MV0m{gzUuJ!;aCDc+ zh4JsrbrQLYHG^^c7eAq=@)sKt@*8I~Jk#HjiiqI`Pspn~5N`Tz!NR}%&nhea?XanQ z;TVQunK9cMocuBbTe#_iWAYF*k|A(o>oM|$l)O#{&bJzYmJqc?fL2>;S4%WMJB{y2 zqkX=)JLlofy}z;WKZ}QumrEbyqW1UZJ@3nmU2k~kL}qvpp{?cR+h`D4C2pXe^^mH4 zlEKj7`t=1Oq${)rH9u$k;a*E?P}%pvjfPItf}_vU@_H-o&S>dwvrCh_XEuL*isFyO z4M^b-LWlX7DGTyl3U0~9?)m=wF-acfL|fb&c=Cnq)*|cmT2?P%2gThpi0;bmJLZnl zs%@UpuFvYgK>4#MGEbCebWy_`AW-WgYuG3Wec!y{Z|Fov7j4{@(e~E;{F&@8 z^wuETVXSw^@`%8t_{rTz+1Z=jC%3>o_mdJ}$G(DICj&v5a_8m&W`6Ocz@N8Y%Yf^G zBhwY^gS>2%Gx$>U%6)i*ss{+YMw02(-a<;spHvToK#DEGQU=gC;lnenNp#nxkW>S^ z*v|eD84^4Vg;=4|I%HTOOIQ1s`nReqa+__{ zT%C4f##sX7^M$$piWO=#{7!_ofR&2J6k?vM>DArw*?LFEXA8@~R|Q5X3a!QR0{3yB zdH#wOXuZ+50NHQYRY;TY;0O2d!7%|6r5`*wu$}{BJ?aU`owV@{$PYPQNT;l8ww$^w znoVB)NSNV}$#dVCEr@8ee+s8?-*4ZWTl+CwYb2+E)YoCd#*l#vZLdw%Wzc~D?WM^j zTekfCs^Yf=PyV~U0m3YyRR;-8rJp1#+YwKMfOOA`rAt2$H3j?EmjSP?AjiUqrVvnn zBQ21|OF6H$bJj5lZ8-;h&*i2CRIife#uJPgX^L;drHrI!dxB7?k=E0x1aaEfW#|Z5 z8+lYU2g{9zL|kA6(<}Kr(atn-$x(N^s8?Dx6QPQ#36k(^%wL00ysxG~wLpqjo-w)5 z>}49%=$oB_OH*x$<7@mF*9uU1^{SgwK8v?YF(e{)Dy8vKe<2Bpr1j5OhX_PbURAqO zoZiS8lPn&;my5uB%~)V@nHAPi{Dk9VAWJpyIHv6HokXGH6qEhRc@G zYAa5u#-Vv;B~B!t$%~?v>A1t>1u{M07JBK~Bx;k;*_l!@-XtqPzM|Y$i@ZlNgy36J zM50jvsqfi-beU#9^j(lequo$ymQc&}Ca`dZWy5?L?V~!+uZpV?w!XiPB~_F52hp1H zf6)IHCS1A=#{HL!Ax8eMIvFpAU;PqNeqtCN=3)_l_8agzXT${R0juxdhn;GlRN@b; zVB3bE^mu(G_xmurb0DUJ^$_Asqbl~y^C))@tz$jj2ky-LIQh2o!ONAgZc8spt0Ptn zr6t~vj+X^J{~ZEelaca6xz1?hki46;Wdbe9Jcl(c!<>SURy=!aXLcY_p>NEg{V@mt z5fHbeWq&T05lh(LRxRd>V4XSwfcUmr`S??&YDVAeL3J0=foC_?v`Z$Gc^ryAI5a;k zh25NJ3>idLENc4Izrz(#sq_SYT|;1qf6&kb$^iMXeZ*r>K#kx#Om_rrc(j0xtQ1(xJ6BR zU`VNA>|qJ2I`T@`hi9?U0kdm`9;fC)Jj-{|8nHqH192`aZ8d*}Q(S993!OFuZx}=) zWaX0GD&MX;2p^}`(>BEzyMU!eEM1DQ?XdW(-%sIxidP~1^~2BsFQa)>0mwvzDL7`e zBNF5oF}n!WZGcV!?jQG72Z2`rwDtkbqI7C)21)@Qt~~&?caACBdZWTDb=%{o4T#0o zLbcYZuiyGbr8o*WcoAp6jL?)44>Y32RLWrhx_;XyqagNQfW`o2$@=NC=jB8MN{rOH z&kpB&A)s_SMO^|T)z^V@uE%2n5+ZWwA+>R!SUgl>g2RLmB-Ny+!HJ$>BmMVp5EaV$ z(M+5#$$1g4cJjn{dztb+3Tui%c3N)Xls3kE+~HL5mr~+=lPJ6*#cEKhwTRb^wUf** z&V#w$uCRp$KAaUAO&#$Fy@4)iT1OCmh>CbbHWC*~krHEUF{1~;q&{t>J1r}Mv6#0U zEx;-(aR`_Bcr%KMtmI#89!VJyY#Piyxx6ahj!8M=oqcmS{|yd7_a4J%PgYZdIKVJiileSsOEz8DzTXMTsGR%}I4=qU)U zT;B$TUE3g<|M=hf^oUZ;hssTn2o#S$XIRYJ&(!=eLLWf!;JD2=$bLcJEtm;^l!kcM zXtY6Nz&4-F@o%zERaM%OnZd8=0xjqJnjqy&?QYO5K!Rn-Y*O*)yjh|a4%hX3I>!yy zda3#F6J4cGq`tY)GZ?o?BQp)U-{{z+fO4*fVkrHaa*W~|LfES-5;;)1x5SZ|xq8K{ z+epQyTa{z%2%+}$fSMPgv`F#U(@%$2X{1T-RE*DBAyyR1`=IN4XzC}L82bm#Ej?;# z^N~EkUp_VSeSM;|B>F+{mr`Ic(V(Di;{vrHuUwIN-)eQFniYA#9`wX#`WdYzg}XUxK?Xl#k$v%6?$qS(VqHTG=4dB3O6jf0ul&%1dD$i z=UOo;tBjC%jcgLCWTxGj?OTbH4t#P^?>N;cmaK?Uk zH|x^4kSs~gryw1kKbdAK*_H`|7OUAKmG-vvc}RfeLIfM&M)H~S|xom z1>{1t1F4>8AwPe&>TYy?Sc=4wBl#iZPVEB637+UUX8FB>PS^+WI=Y5b8WlSgSt`b$8YnM+uJK+~_gWW`wNsQc|W z>(3M*#`T*?I(chOdwoL8hKb9-10-f{aEe2k%t^0#=JhO*v}741isgt_fhaEElkCjz zqTYqF8O3iaMcqBi$uLzT&0;gFbd1_g zn}K=-OeyNjQJcQpt%lm&;ZP zka>m_6Fni@s;mxD>xT}=hY@x7VfP|?WkSTvACaAr^(#pgk4sYA3Vh6*2oh2yu_H06 z+hFy3lwTZ_Z-=)D{qPqbZ9hO+o2dR;SEG!V-%zDFO;Ei;}Q!BkW6j;b+i5=W|@AT;|WpJvDo!>3M{v=1)cks#EgkdW=L zu*W?(S(QwU+{+j`30 z16vS;gZR0B3j5c;j3}pf+up&J9Jfl4ZBP<**f5UE4Ar&_&H1eUEjccZ$k{Udap5Xt z?P&H}8+ClCq0$7fiVR7KBOPAdO<1jSyqSpS0&Y*4OzhV^A>HK7qj& z<964+zjc zKT)7(0Q4Fz9OzwKzf!M}iC|OU{2m1THr{u3*Le#&C-k1h+8jXo6_c6p3HTonl-EzL z6aMnB=IauLi!e(HF|Pa#D}-?i!IcUqQpC;Nt@*ebr%6*%l1^#NbLMdQ(q%W;0eT`rxYsv+h+9kOft|8XdsG?@u0gyDUCcs)mm zLyGQgzJiQ8FZs-b9se$v)Buz>(RDPybO{K*!T&4Z0^G)8<*N|XhaRx4t^N~R&;(jn zz-MgFUt2?0bvW8wq#w@}LS%f?y$0keGtnw>rjlbls*iggb5!P(A?e);C_|PLY_plU z1v5d;-;I2szR;IbT({RCOo4!&D{*Q?6zY}s9+wHi`RV?%s_BtowJp0UP;qx+>j4`& zc9s9!ly(y3=H*ZMsw;4b*V-KeqaDYQm<#=VoukyCmm*k2QoRqB-Vm^=Z?wGbm?UnVy;UrPUk4ex@%#;wYo8jS2$aC0l`2Q?(A1%idYy;m{7VIxDz9 zO)MIRZx&FRv8$3lP5;m_$z=Zo&i}zxj&_yfZ@dN{jpR1MOJyUJn%r|}83(@prdbWnDQtA&>Qq))zYIQGaf%mVlV&rES`1=(rFr^3 ze9LyTEJb`aA%;UxS)(u@jGRe2HL~a@woIpaWool=l_b(CNUzW$pV{{az^Y%&78$RO zrx}^L*uzoa-QO9W`TnZ)kz|YA*I?gsg6Dt|NLKp6*C9_8Klmux88G7yWuAX$ygZG# z{=T#@vdb?Q53>jz?>qk+Vt4rVT5GB7Z#&hF-2FsY$D=UNi)YrcGXE4v$LoxO-!3)# z1{ z={IP}F@_~VC%L-U9W6C0{5H+GcZe5Aiq@gb!9ajn_RbVjyk=)WTw6cZKN?Wu*)j13 zBAA&UrK3h^_mPBDFLs^RCzE=}V75=&h*#zO1bH(-V``RGhVwT=LZ!oQjPci!VGo`D zr1$~KmAD(lhRI$JRVo&i%j1)WiW}mV+1<4Gm7L5Q|}CPwZA9??Z{6go9oF z@;8qCcUw>hxla8u0)(%{g01o$Cl%0_N>jPLz|oA3QzZ(jw7~ZlvzZY9!9GV|?5i9Q zn@QR5moCOO>mS?fQVol{6mAmK;)(9p7_aKt0`zwzrI>6d!C;k|rcMcZB{A?+35$K< zDsmG7q%5h0ut&JL(K@sQoe6WzKyzK>N`#e^=EJLX$N~`{GZ$o4>{F}#+3G+n2{7M= z_ocXACF_pDFx4pb%^K8Ka3Y6h0(a7zeaQD`K`7{j!Le+PhAnA#{pZuE3jSN3HDrt^ zJ5Sw;$K|Cv1~zry!|EP%uw>19>;r)JkK>3Cl{Z}l2mB= z2V`j>-cZQBQ1@%R?HfufVsI?)QDCxOV7ejwA0o+=swRh?%07QSp$U*TVe!f@&oBRV z?j>XzXT+r;?rC)3#NWbaaUBR(3KLM_2-Loi#WDz?9h({I6R1@XVY2h1tU;l~&gbC8 ztm8mE<;!OHwruvDGOmlRAz!;=fxe?#kF#ULH zDu4*CW}b1kp#J?>{kXaO_7^Q-uus+qOdo>Py+<@Qa&s1uN>=Ga?gQbvAAr(sN@9E> z){Z2G8{gZ*4}ZKm*%-_Dw|z26DC!Rql-7GXD%34@vi_+l(ih$^%cEnU^A!(zJLYt8 z_}SZVJ39FJ;e^==kXSA@}S{7HlJbbLua}=ei?5827GJxA&T|tX}3j5 zqk%EhpK_M^`}&)nWddL^##s;9M0zIojs)< z@$kg6)RG*vvT}Ghi7d_M&}RJ3j5gPJp6XSI?|*deDzp|!Q}yAl@w9*r4T?^iJy(E9 z1;WRO9UVvu{ez{<3_0que`(7gBDE>Fea=6YOU4!IW=!HjwhE z;{*W$2jpt03*?KMv@bRqz;9;%fLUq86vr}8qmfjI^$z8PT_X=NE#S?hxNPPgr&oZ~ z;$-;&gp((E91+O&EDMa;_z46?YL^Z|6U` z5o;n-I6i6Efv6-`@f#DL-C(3u_^ad@8vN<&z0x^xLeK7xF|K1Yh|Nul;nX-HOrrW^ zTh3`%vXik7rTVjs_vlax{&B=KPg8TJTV4{e+r%;l-dF{ER-6S-c2m3iePCleDc`+s z32Lja%4a18R1!BY(DRJ+%)#g#4c_uL^}_!R+}Cn` zyvz+q!{#}VRA`Rf{Mlu55o_Yth@Xbgf(eRzw}AoNesreRUmUDKTi`f`H#Fl$aG+;( zs@8a}O!CEd^>Y9LdjI9?y0;T%oS9Fv)n=>PG~6s2xy5b0iQn-GfXU3eQHm4OOHAY6 z0~@NkQ}LNNbE3zn6S`p1qp;y8}T5DD5 zeS-@-3=@zg7w~1VL1cO`de$IA6DLH+1AR9pEnuYHdN8gd6*B6R;{4@N?hmU zII&7Wp=u>f5@C$RO*HPSwHV(F`8G9*3mz`U*=+eDAUoy%wE*2Qu^|UN9VWWVjw)J{ zqH-pcj8}`-jP-=x5At`_&F=;GS3guSd#FZFZ*%L!$mWBs=AIPcAK_|HFF{m9e7L7m zr-T8=jL`Y20cxQvRu$~P^}hoG>zMfTx&?g*djk(w{>HnV`%R4k=!9$lVz*J55su6N z=_}!=m(42f>VM+rrZ~+)QVJsc#)1ieaN?6B{=G&SU#5FCFVY=6!%?l%{;h`L2&rVO zYTn>zL>Wcrk9Hg>wf}xx5gkb8Sc4!a*R4h*et0MXPmh?U&NnsRA-YwsjN-Kgj+VI7 zhLk!ZZq*=^&LcgvT?y?a*77xer=t(C_RXvg;qysq`3wkPyFk;a0S&4&Ac=Agby!)A zXhJ4P2>j(7rQH6eW~j5Y+|LYF@rwCy{;(8kf!P(A?FV-%%2WOo_vA3;3O6Io)|7 z{uzH`J!!N`{q>8t1D_f`rx2PjGk>Zpi%#!@((fXiISrB^)%B7iggM-5$+bRjkI+W2 zG2RpbU*|%wj^{G_+3lwGlCr}IN?kQ`>z4myj4=`W-vZ@}bLN_V74zxBW3f+Y)m|4{ zkJE4I?eQ6MZ&MAp40%`a@-n#0q3o`sX6mtiPaX)FE?O+at}9` zUj96WCNo%1*FH1PP;&4)3Mz?5_%)7E*P&)?{j{t_UgMK%6@Z-?n-&qB&E-kI3Oyu* zsY^csm2xWi6A-X~)&*inw6{aCW2fH@L|(N_tk+UmX%y-sBN{B^fUI(XjJCk&tAN)g zR3#{lxctb7Unk1V)jz|wGfnptvc)dC_Fp^lt|iWp1i$?L8g#v&+#FMjJ8HG|-|yl) zQZJ%iE5-`TYM8imK<-o=tcv7`UcdwaFb~W>RdzSrNSgzeFjNY^Ba3wjuYkX z>R||sG%AMuvn_^UqE!Ussvm&G#YAdpUn}^Vgq{SU;>60mXn;)5D;^3sxjq>F`Flgc z{IVrS)d6`mlE}qWwi&MMxKFUk`ek{3B6*Ya@EUP|J*Y3SYS7TiJ5MS{Cl2$@?R)?6 z6X=HH@La1TfV@)`b4PqvZUM57_GF=FwYbA|y?${+j=}}|@M-=!=G?s&0>+L+7QOp8 zNSjH&P?|widZgXTEU%8pJL8e~B3S(5%^Z%*xG5OR?@&rpaOz+g2{p#EeQ)p#Zggw|;0U8V`PDpy5E_6YxABZ))*(K6W zG&m={y!ly#2@8=$f7C^)m_62mevp?U5z~0wi1Hw472k||ZTOVl)hnY~ZJp$UT)Rk( z?NpU74VYWE$$kJ)3u8Mj2eVU3t`Hp@WpM)%txgDiJM%!`!0m0%%;5X>{Kb`cb7AK~b~-yEC0%Q>XCKA` z(fx@Xmhzo-B&H=H_>*HYjxDfoClyERU zd#A$tPLbR!^2DNGhV-Y4`>oFg_MI}ECM7NQE>#yaTEd`Owb1omO%iIZgvKtORX6<|I(H&`lNE6ze?oV# zJfn-!eUqN7hy5rhJjQOCK{=v7%Re7=LjGbz;p4Se{kmVt{eD6=34gtuh1M@ClWH`l z*Wd0$d)wdQ;5X23`t4a7yhyoHwrU5Y+-WF3NufOT3vkbvrJ=e~$MQ|Kp*l3>4{Eo@ zP}}SXK2lnB?=d>93|0-!e3W;b<-^I z39utDMn5BEe`nPgS_`~|G$~69SRpux3|SxI>!}1ijuwMzudaV}4qA3} zwwx3eJY7(m=268sWj~arSl8-KY{l6IHO_z3X>;u(Tpn~D&@ZG@$Y2z_Wz|CVMl7QB z6N_W9cwr>U#*`2o8r~3J>?Y;M+r(YIs}SjnZ&P&9QHAY@rUl^-wPz3le?Sw@RTIB0 z#`ZC`cgd*_55b0TJIZ-2T=MaPOJ!CE5YH@-8N(D}1VnHCMuV^+iv!50fG;*UpgE(s zbL4%|S}U%J+CQURySc9@2mgnKx*kmoq625@Y=X&x06~rXJ2ZCRv5gA{`^ zx&A_BD7lOWuf%iiq=3L#Y3c0}?4|g#Uo}aECTQmP1;-{iI1)5g5-yyNZv~G&4#Lu@ zEtBmQ7#QCbkQ+%<_XOYlGX?MDYTV5i(=g0mb*m;M-!~krDBfTk#|hkCl}FfOYP5YR z8@E@0NR$7pBKKdHv=6R%LRwR-b7*q#Q?82Kxb;@@V6sDdrYbj#L5506z${R)j)%xp zkp5Fv64g0fYK~EopBLJIT(CZL*;-7^|3$-8Y=-}{67Vsqbq3E1KO_vnSOSjsMe?Vq z;6RudLdkUO<3%gQ=8c&nkD`<+$)gR4<6tpANigvro2%z0_S6BHeI7|mbU^J0%3o}Z zD6fG0N@Vo9^<-E+-C?g5X?hYKeU|+N)^n-VZ$SCOqCsJ%lF*pu>h;AFLZeaE2w_da z!9O4W9n|AH{TB6$0SQr-QFa4wSz}DDH`Y%b0G5pJ1HxmPKAe@F{u zkqT5eE~~^s)Lg8g%QF;LzOL{`t+-M4x3m8~KNycwgv2-s`!@=T$9I$!Nqe4cvkq&)UOZ@#KjT5Jj2hg0zOIU=Zr2=C7QqkA5r zQ#hLjw^XBZESA3UOS;{m85(}@(VBnTR@8vZRGjH@Gy^t-x2R>KF+3(w8#}o2*DLM` zhtk78wJZMczDqTPr<^+JvwHiJ*X3vcF;-j040&gZx?WGWC7?O0A=&@B>8C>6dC8l4 zXsaVbSajSB*`x)dtj$dD`wF;vt|NlQnBnwlAXe4Lc! z4Z7z_Qe;BEl(lEYEZX}SnWG}N%d9DDt5JlQ#XPC-3?7KfI;5%5l(wO1 zv#i#^47dZ|VuO{<)CijZH8Ch#XL@XMHzygZh&-q7Kp<(P9RwfPx?g`*jHz0MAa2HV z+u{6y!uyi4N@xG>cubPa#%*8HtdPB@P(9^iRDqRZF^5`OdRTojd*+ z>Vzkah&j65TLSz@n%~P_da_F!3O_?c$=Vp^zcT)vp1RFuEiSiPaMa@dz`nUxe50xr zXWy&fMiIxs{gQ%3C7B#cbo_zes|)G{i2s~{V>xm>-#A`kV5|bWlnPW0pkwgg#gn!i zt?a$_>#ZE1lUm|?V1lW;x$#FY(L-=?a>bFZf>pd4RbVjQAKVI_b*6|Flq){tB~Ii; z7+1%myG^4zY`d}I#?OiSZQ$Azff7~5cukc=9f{fjI{*S4U?AT?wle{&KejT_l5pqt zo_ipNyrR_grs!Qb*pyE9*xB^8!CD(%dTMtQo{#Gka#mXVy>{e_+fTes;I!CBy}R)L zYm$2}8VjCB$^OKJ;k%f?UCrx;|Gsy`xo{++zg5mNDK21n2L3)DR#(2nQMQ42_e&6N z>2LqLx#6x=oH8?r_k)A%h*lgk&HfIWxlW5n`g)X=eE8?>@a=<8J47v6>Zw{`UF_4! zukPMu$n+d>BnFn|{GH4=y{g$?V-*j4=$UtV+?m%8ADoss4(IYFqCenaImTa$6UDos zowV3Y;0+_pm~|DXHY!smC^=bq4$nvb@R?Ay+D+d0HJ|bin!zi8=KTX?-Ve+}Qoig& zs=*ZpfpKL2C)elaV%2Ik`&S{Sg^h+E{FCdATiv86tTie+t_{)kA(!jV$Kq8e**shM zSUPuQti`>aKP%Fny1URee=(_iEvkBAa9Gd``2zNuY-uy@L`|`H2&EBaJYOdD?+ra~ zAi+49l{${UvFcA>zP;CJzxOo9(vxC%>1*aA>t2czUZSpKu$dTyu5(knG1je=IH^u} zB|tM0W#2tH?EGkc@NCCwy_y$Jghm7QRuL9LP zJ_Y5(!)7;37aoPh>mE^(uJ5{AxZR!*uG4C__qMt5H~waPbinBJ7>P&k2Y9F+2G)Fi zlrfx{=(fZWu&wV+cbA!YA*^cRqe<2q`^%#zDWyIvV%`efMwy>2I?r?_mj+KbNFOFH zaRd?3zz3lpeeH!VA5{Kj$eieW>N+>$d-V>9t+w-{a0F^&Y6~$(VYUY0 z6eo&M*`-n(#8G=7{q}=Diu@;52v7&)4ME)46~QPprhE=$q2&gl$VAJ}AY0^H2-T#< zqA1i_)<~;2kil|nU`Kh$9lJ`4QIIZfL^s9;2IZ!B0q*C#WSnT^e zH@EfK$(W&9K7as0_-4-dek72?N^x<>(@V%FX3GCAt1qh{TvsVxM6J4OQsgEq>g4LV z?>M^){m#0XWYo>Z5-5m91Q3P91z7&j3obiJYDF}p9zr++X20w>RqqEquo#018Xu<) z(3G#LYuFA8T4cN+Jor}uXweoWpNISV(_vz)%sXH`{GfDriI|F9&}(XmgZIYwSC3o} zEq~`s=U}uOslRu1D9JDXPM?jy(%xF+)$i>-W4`Kq|E?PbA`^{KpH-lH7|@tSo|RiG z`O5p>C-@Ll119WUys8>^noHEWl=1pM8Wev-L1@g4z~ulkNTcH0%-lRcJ0Tkj4hQUx zwU6T&6;zuB^#)#jc&S6vbw|p^(uI88J4$UXpZ~pvcUke>4jkmcm75FAkHC+=SWx7Rnd_elCRTim-&4?5lk`;Pixjnta~ z6vzum+q72Q`-|UpE)xfq-xNDk!z)ipfH;r)>~w&^aP>U3H_RbMF)VD;LTt&(%PRL_ z=i!j|51~>2c>9c8X)h*%Zvk@NE7S8$^5q`ALiJSTmd^I0KlIC2OH@x^@)5ExEE#=# zf3N*$`&muWXHQu1qs~~1z4%hRM_u&FpURZPlM5P88Q|ELqyBZBGS-h?iuA*CX{_#D zXicSMb`Mw>UR(v(l)|`64Km$2=Giu{w3lDj$p73)bnrMV(3IO-jC>aeDAwQMIQ!~Zg!BgiHm}VcDy^JFI^354zrbTxST?Kg|S|5R}XW=KK|>5jCGk0Dkss_hQ(65v{)bUyp0m^dC_ z-1)O&n!}7&JyWRxn1)idjZ0wo(jfHChvHpj31a}; zK}#;gPWM+c!VF99jNDiAm^W@H)cTm9wBZcYS8v6`gY$TaJC;dCIKw{jtP#zjnU6ns z*qAcFOmeJE7&OWpXyw#lrt}^4TgD?rV^xGNB0ZfmHb3!#vy${TZPhrADKd{O*g)@j zdZsjy3s#JEU>DcCHBB3%Rh%giUxxP$85tnFC|@kDP+)*5d}oB&QO<$UdN5P56yNd> zFxr5=aUVCB7M2s-fb=szpcryLebP!Og+_dYHK&owAYUQf4ewFK*3_vdj%1R*sWEmn zkbxNS{8ZZBwOjZuzP|UFn=>?pnw5xfo96TgwaHLiS%fzBIC~75pYvQhB+O`l1m=yq z+1+`G$39DF3;#*p0b9eiT&h+j%9jP>-2p@bp`q}!d>(KXgcelDBrh>E65VEu+p%k zNQ*Q}!_wW7(v6gK*V2u&^dbUE3(_DRB8@Z)N_Tg>&-b1A&F~Kc%rMJxKlgR6b3P~P z$WPbA9#{n(N|8xU;ZXCOX-v+zX_06oQHA1e6mW-)?GJg3v#^f*3*QqaNES6{ruDd1 zEEp!VR>@8Y0JM(ApnVMe!$0rdE1pb`zy73Ue*74V;*MPSba%G8tP^wfwE^ zkMgv&)`We9sJgW=^p48ydliNWfbc@fHB@mXDFxf={?SjXVd*V+?Uo+ zPXDf!zNq!az$}>lkgcY2T_P)Hi0N#DN9$}6Q%#I4S}^}HX`Du705VGVSPQHlw3aH8rdd z6IE`y*!x`LSLspqbtEf^UB+jt);Px@4Jx{^|^B03KrKL+q5(1%-;l= zMREa@2Oy8{8Nmn|jlo!^Rmbem0T#f(QIG?D<(ud+IGfDttvH&_tdX0Blch<{j}_Y- zdZE@Z3{D3^x`04E2%X~Gs$VXU$KUZr9HLvepaws!rFi(E-m8gQ*6rTTI!RJPw~({gPT7 zk=n}-u&kpp$jpRXBCtBBx8kxWwLZyo(wzw~WovG-__^c(eFQ>~cb)nwfc3+}epplt z+abEfNr#$edSne%#|q2rYlTmRtSxTSa!>H&t{ykal@UfglZ=N&{YF~I(*jCK-MH8$ z)>pV{{$>-r;Duxct`fd^RoohwfKMP}4wk$e$b0+q;ug=Ge5Y`B{@F9;dLvvAIM--5KCcXsI-j!?_G z^*A;piCvlyPpD-KosyslE;o~u!o^gIIm@>e7xKx)6Kg6=lA&P<$x0G&kTA~!(OBB-$q;A64U;j(ipgAa0GdM7${@gy-i zJ#yaWE=;YRA4OlhR2kfz%9^oE)2nxmGGQFD1OxV}6d)-Fc`O%L=WbcLj(BZkF#-!~jcqC*q(oq6re786 zo!23#FwK1f->lGlx#y#kwD|bDUm4u)dwsJw*w2&ZC3kW_>DbY+suj3F{i|KW>FrqP zcTBzI^S*hv9ZGv488gz*qtiJ#%tl8nyY7H*GdiiS-BhR+~X&BEep3@tsEQ4)V+SYJOB&F zdnFBcj~pKB`|)SLhmz*0q%Iyj{_dMS?4qeN>iESF`M9Vx&apGt5wJd`Ra<#8XLyM6 zej##iyMIPs%Mo-@dO)hn#iqBm451-z_76`tMRCmZ^dj5nfRsH;H@@5)>b$8~Sb89B zOmJe}Ycf0D{KLBybDAE+f6NZ>lU>KA0}P9A91Ua&d+c0 zzdO#p{%WCA&98wPG`sM+T)BuXqJp&qlG@$(A9-(2|Maz|2! zsXK1Wx0m1D*Y~EsH=%B014%<=E>t19=L)M5SXgTw zFW&n)vKYq%!XAE=wa5~)Obe7X z!kCr2G}F|s6v)p(Y5yfS*vJC%O5v<%Zvhfy))ioD(>eW~2a5_vp5HF-DhiA38J{#} z_)^M2wJep?%L%DW@7XmjR?w{Q^OEA>w=Lw0Y&1pA zAni-X0uxQd&bka5o%(h-x4SQ(M++QK(+WYFPBk->U+=7pxyxQk%3eb9i7;BjY)L$- z*^*e@am{_JXSg`G$TELzg>DrTC4P#$Hy5fyJsqm%@Q@e}Tk|!bab?_yu5&w6Z>Yu` zmdJduzdi+TrVN;R^^9-p>5=KLi3c5f>=<$lzG!Olv)4kUgkh@95Y8=!&ChUq0wJz9 z@`jdCcIxOXoY zn>xRGt7bxSk-|e@0^%9GI588ScRieJjVjBgKJPMH?x#`{&98gqLA9Bh>Cw^la z1r|x^eR76odnvXR9>8$@3Z_R%%0PiXMIRT352#k~-93 zaE>#16Py_5KZ)Ihab1!h5_D)@Cq0QQxPUnjLtV3Q&2ahOWdfn27)s*fa-!-8VvdU8gzqs=3! zmYK}wrP;r!m$wn8MBrRVBO^42$lg`dVt$DwB9|hP zMA0}60$I`kU38Q0hp!WzI&2k+hkgADe%7g`u0|H^Aw(X>QyK^XSe~bO>2ZG{(`D8X z*@GkRXdR0=CxI8Ex%;=%wCmW!VIc2K-!}sbBd%l>dy}Iq4(lThK;_Up)3Xn~4Zf zQ|HV6G}1HfxrIlr0U?=WPP3-FajfiqW3lt2B%nKQ1Y!()Pt1LWi*jmd`|4gnv&vZ) z(?5qX`sNb>lxM=^CZHD|*krL`X%(3$^{vbdL+kQ7RW*RR2KN}8W!f-MMTvJw=_htG zU2g;fzlswC5t`3-=HqMaR>9X>NfPU8k5Bgi4v3=KaGP8Od!Z-S%hS? zX&ecs*FjBcQN-N)Wjxd~J(eI1L=n2`R^n4#&hYwZzzGZ3VO$YsA_{e=0yhW7Zl0`z zZO?>;({zoZ|42cf3vDT(uDB()FMDeGR^=j!0#mop_HJ3 zo(F3DfetevDxukI9HHt&{-|1o@j7_kt7i>K%xz7>BiHx2(yeq}d7mN!q`3(Au#C8C zrM4_@9c25FE#13ld;>u5(=V{(FI$8BJ}41zo7>(mK&SH8Og#kwT5Js4R?;dV)f7-u z^`wyW6*7~mx5qRRc@r$zIbD$OicTelpT`+;Va?B?=uwcd+fCCu0)DvM1oP{8o~Imp zQrm1&pWjzy`#M81HfMz4Vd8#w292T0gcENqns0iqx^%`6U#e)Tp0D#gvQ{Yfz1oub!uv2A z@U4sDHTQo(Z=@FQLfr?8+$`}FE3a_O+0&|qtB&T)^>kBFFTCtmKUZ1;_AqZoOQnFu zftd~~@!AgYyC;rPPsv_=JFP-bJghlP+k)7;Asixy#$2Jisnl#*58e6>t=|qw*%dk% z1>)(08bO)xsT98erL>uVl6?Q$?Ua|7_2TMamQ&E9duG767**>uKsgfgsUId82w5gY zq>rPdylfi>C`!ZsX02D|-yDJ5vBEJ0GzS6~8a*Er94A;4KI+W+M=Gl`@g)6juYQjb zk(!pP#DCDKg&k8*cLA>=PIif^@}#y|V(-p?-Si;6j2juTy{tzfI;eY}0kl5HR2^jLN0YReiq=Z208-qHm?i|JTgBr`*of5+@+L3|v-; z0H-lpUcFa)@P#r0gE#hj;CVnq2kdlnh$Q^FAb?v~tDw<5lp;Rv8NN3EB%yd zqD8bk{42;t^x!&Sie4LsI-E;Gh)-zk<&a~U8BHmCWxQk*SHPz1ZKFJ%9`k&j5Qo{; z+@~i20^`84|N3+33iylWGVRyS)b6%~GVHBHavHZpuP>_4SDq>1he`=ltb#>PFdm3m+KCNwVeFd}-nK2prQS2EN0x$eUkc5NW{n5-EXiie04YcnT)Y-bjHnxk zl2si$x0Hw!mZ~WQ1NdVaDAYwDUQwDC8>r&r2IcNkC?y%&B}YhVJjHQN+NZ89v1lqC zAasp9;z)tbKAvXFiXR7JCf?4NJk*?;Sxk5YIH`-_-{DFQ3TuSpT7tlFF7Nnn= zzo3cEC2KW6bMS>^tw*nl4B`{{R&k2Ow9|aXA*>fgO5eV~n9Uo`B*gfTEK<)VUuVv4 z-)l)ztP^iu=2AsxdHO%Zd{JMq5@~hD^*em`c6oVVTr0V5%F8v zIGbj=9jXZNce@IfrVx32ak+j&r;uq8Gv=$-vBygST(v?INnf;fUNc%-2Z}v%j@Iee z*-)ES1bEa$v}rY2X&-@3m3LLd@&1B=ISBCge<#%FlEE|R#=q{dm>#VpXw!8n!tP#M zr?}l&7w1w+$5LH=8YnWj_9rrThT_sVL{KyPk=9mlC`TT5=7Almwxk!JV`swXTlL{^ z9p_dtorU=}tP6h_cKg1}P%ou5=l#ihpzYg6aszC9l^Nhb-#gRbiUo8zY{ONYl8!-y zx=Iss8Bw;apYkiHAfMqW04d`ocNjg!9uL^WCnhHvAx}lb&YiyQEav0#_$PLEca)Vo z0Pz3I>_yK07u}`4FYaH&sk}O_U4sP1lG;T;1PNClvbvee?;q=d8SJ-30MBOnr!3}s zhmD9Fy4R-5EBf;I^CK~5w8f+7UU_BGK+L|5&N<0Wk{Rz_^AK!aQku>S%%cNI2x&qJ z#{VD`T|G>n77Kc^u8J68QLUXgR0v}rJTM*I`6b}+LLk8}M=ruh&Tk@tzqqx#+45tJ zhO7eakI%tGJ?Y~k+Rs#2CP<(cR8oAi>zOoGJw98BboNQW z6=UXg5{T+M7_i&9j}*`dJjwCRA<6}+T|P+T@xx0>|6&60lkbi!JjRzj(n597ctZWt znn3Ir4*pT1q9j(x%`)FDiC8w9!Ro;(Gn`H4Uj}W%1HLmW;ZFqkzYe%^>08meft;dG zrY@S6XucV0w(R-Lyia6hul^hQ`4{mdG{=RIZBn~2I~ttsi;;s zt(pL+B${FoC5WTK>CQWa&053Z2+5Z5TUXF%Z!G``1Hy5*G^M*MZrk_gRFc{V^aL>x zzjwKE+=iq)2BHPTEfG0O!^G~MxWyN>xMueF&Ee%%+Dj}#Ve#CmeYv_TgatQ3g(j)4 zjE#r=BPYZgIH886TN3Jt!{QFsxyXZb4X>0na{ffkjUIX5INVheEN7QB8c*{v$A?DW z^=tD7BLJ;|v$XTri$`3>C|bINYQ8c4cW;yUBV81R#sPGIRI1&e$n2QKNn#HFv*D=t zOTL`(I#OUyM@?^ne*Hz}a%OF_CB_~AHXL#qG(KA}QAa)9-A}3`5cKy`Bq*Ex0VxWN zu&)k2XfbnGZh6oI@Ng>**gGqZ$z*_QWaq*5^u#R^Mf&*Fi6SlEzLHF4 zX(|a(&50?ewNUMeG-M8}{zLFODwdfA-W)^Nb^hncmuK(E9lJ7NS0-$*Mu3sY+MU=x zg;)3n*0^42Prm+Owjf^Z?B4K_qgD@Y`+2t2SRofiLON_NzT{LcGWwUjhyq?p2S)Q< zCc5!X^0D1aKR^rbqGqlyKfgQmIHm@ZBP_x9dGW#KznD4EQv%ZJ-t!96A@zd%eV)$> z$5e`N)IPaEzR0hi!2}nlpO=RXw4nP3dK@GJ4z|_Le!-Y89XRJuXRy^g)KX4ao8R^5 zupSXVF#X6E`c%vx_%-Ck*W`#^JQ)n5SS}cV5Wu7OO+5&XK=`l1#3?KymNDt^)fqm< zN*8!O^hBbb=85^IWm6IWW*HMszJ>D^D8`0_3PCnKdqc%Wa1|B>e*V?Y`fh_tp@UX+ zm+V2vBJFr&-B|0b^Rfv#Uo>A?}A@@K0H*qsiy2KyW@(e-CY4V;V& zEjq?5&wLa~dyGiJczK!A&3x5Y!IeS3#Q0u*O!PvP-zh+*0f`b&T13mKcP$_J+x1%6 z-oa})xtKLDIk1;$O+i_AQU`a%z@+sj=qQmmVtKpm;*RAu-}N`D9BaAY6_$x>SC7og z>z>~j3ZzyOrf)2ia(3@$-nP=YTs)me_}~g+v5gMROLK7n|2IQP1$EfDqIw0VK$0EO z2CoE>`x+0&k}OPr25OXFzt#R*f_#^H6O#W`NT!L4Ikj+$$Z8udj~fb$+J@IlU74kg zf3{5%g2_L(P{mbOavAWcl`}vkGqsq)agK?Alt0em=8y3dcLTyJ(vyx9n7!hQIhV-Z%+);(OU2&*T;r zg8^VcZA0m10lE zN)Si{zea(gI*UB%bV#e#RIk4z64YBk2i7+j;L>e&Do~&|y z$JB2sB{d_k#nlX9ItlyuR-Q@R==VE9B&`F3u52UbKr(`Tk)0^$)u{c$c+l;C0uPq3 zDE|G4up8FkBW;K1_3SNv0~i2O^0?CbK3Yf8NW29M)79@P2n?5I0`i@c(_09AfWEU* z+{b>)d^#J&+*zaX7e^7~I{mVDC*^8zgUB0BH z0+k@I9_TBa>fRkD`|3{0*KviQ-UkO#?PSabV}K)wY?NnPqb{UME$dMM>b$+`=kNDD zO*L%qC0Jm35KI*w0J(GU^$)Or_Ip=1P9kkCt&imA%4}dVurWf2k+pQltK&4W>3xJa z71(w2pWJMi!u{R*16P8q|GR4Oo?-J{|4eMnw)u~6{5Breh#l!7knQtf7bP-z3Ud2P zAO1xhh2nu9uJA#%_wgd)RSr0sS^_GnJ2hzk!AMqrn;$MbeC*I493V$>k3aEvHGmRr*? zkaZ)NQF44}{=6R1R^9xN9zt@s|+R(EYl{cD+HX;RN%hXVZwV zLHW6Fw8?F%irsA>ZZpJN<^D9#EQ$x6bUvqUd##LLM3ZS{z*b*TV5pZ{mqSVq%XB`T zW-zgv_bU_1l9v;2(oB7k=iDA}oFdmy)$VO|wx9$7+}a`%ga`w8ru?h?5LlFb(ifZn z0eXW+uU-7ZukHILCP5dQ*M9UE(B8@?-Uw|0^Dj#$i^%DXJ=2k3h>PMF>kJUSOfy;xi~G?|Vqa-0J3q7iV+OL8Dw`I(l6JZ%yY zw2^`|!FnS48{D@;bYISUt@U5w4wI2Dr0Ts9d;@%N-xz9x1q1^C*rzrEa5P#f;jBE1 zG?I(VmAj>qGt^R@LNojOc?JCERrhCR(eB`_Cyk-csV~hl5Y3@}Y5;n2sr}zOoRMUi z{}3ZDTre&}*yQny@Msm{G}nMM!TD|az;f0P&h!u7Jt}9jXr-M~y=FMMm(3;Dw#-oy zdqh+vP4MPExuuj;n85o?=$?Ew`P0950*oL-O>1p?h!4RYXM9tq4kI2MNIs}0G|QFn zh!AcstB!@NU}~Q9G;(vW9tv~I7p`QRH438C0YZZq$G5{T~nU-xL+%9h>9daYFv)`c1$6`yI?Y7MG1kCdK6y6<*S-SIF_ge~^cUP4LkBl@>Dc0N2Cm z%{HR6BLmP}jL~zlT=XI7n>+OmO_i>C|g30Ek>7jgCNY@O*$udciXOVxUvCrCMG5 zR{*u^kxUFg1TdUe%|EGI2eoY@M09~7yS~VUvSv25u@n&472!Id&Zq$3XJqQ^zWi=C zfT!lYut+LXw5Q(aXQBKrAV}kC0m&qE)kzYmiT3K7$}Dp%1>S(1z+EU5R%)NhH2`Vv z@0}iEVUTLHavI~5Vix!hF11lZQ+015{wrqY@3Rln4{?J$KbiFY*xSBSuZvrL=Vxq* z_q4-!pKxdvdl>Tf-PS61Ub66gsX}+vrW(>ZUkU&(xg%?f1lAfALE^uzPIryIGl@W>c%Iszoj4 zie9YH4%+~Ao~$-l9YPn?0I-HdW=z*qA6=rg*@_4~OnT+*oS6i#dqTX4Z>=-d%^lwZ zxO!U`%Ag~?U{a=J+#d7~+|H%*=r08n$jIw))ZFZ(a99A?(G3uJ7^lWD`f2&Y{Ms+7 zEZvdU%RYrEJ<^Rg?SZb5ZWLKk1GS+m&%Sbp0m|%0SQH$-OmXw?r*8k@E{Y|&an0Ax zTH-3qGC-`K_w=?KW|urn{Azsjr=~Yzipesa_P|5@l0AO{^v1Z&KY;DV;d%)CKYTaMRm%-M~vSr?rcZco~GrJUy zz2qAG&r0EuE?C4=rTX`8IM|68&>TK@&yoRU901riWo%*pO!>LPs!oj%MZT7h|0WF}g<9L_?EBGXK;8vUMs?~o(-1VoFyT5?`lZ;XSry7bt83-X$CzHPrbsG_@wbzRoA z53ZP(C<&~`3#9YIMwxn?_?A4i<*aV8aPdN!`b=1V8^Pq&%c^USWi4H#2~3WX9ZIaq z^JjaHNE_VU2c3zgGdSYBb(v}J6jFg4U{bR!cTiP;Sv?g$9X~xRY;fsN$4G1MRGg;p zL;E&%NV+63TL7-kPtr15vw42uIB48S;UT{^^lFXx&g=r*_ce6LdZ!xu`D!C)$0^OKFmM^a$SmIrn`tfWdm_bFTP71n9tkSlx&7Mn8eeTQ=h84J;M+jpef(z? z+MgD`(deskT2Z!oWz)AY(;*JieY?plMZEOQ$=S6!F;bbKxP}k1Q`oLUrM1qSTJ>T^ zjEt%QhvGfjnjY2XGk+D2hIBO(whWRP^(^$-8`y2P|IW!dtBJ-L94d?6xoj*mxp|4@ zww!j3?Ynj>dMYySL6za6M@jY-&#HkNE03+=w4RCy%BF-xDG1CA};e~77hUU`fGl*Ew#r`c%gT)LcPs5hW zE+T7zXPR#4V<%CT{}9o7dna0FWIjoRFo@pMPV?*^Gs?8)phz-@^q!ejU;6@ll37Yd z!q?h=VygcPv&&cRN#TnC#2U4~?lyHfC$NZq`BR`BKF(=Iw26f(I7ahOlD-A)YK}H5 zmx%C7!#93z)MBM_I#eMfIGi=yMMaBVId#9Mpv#R+R3{m`SysjXeBA2 za>-v>Lj-HfyL^&cJnSvuB=zg`74R4)y6+4_MO zm*QMKv&FeOD(9d(@ zPvu7M-cx_G2A z&cS_^Umrc%P9;Jmw)`HquMQ8<<_Ev77}vwa1b$ zr4uo3*;JHT{};F0nJm*Y$rml=2^=R*##pChE5ekQ$9gjCFczwK z5!i1Opq3NpegDMQtMUW0zdhjfuiWr;;+ObR4+s@%(h*H%)GZyeam51reEoMm5S^Jr zzWcC5@vpk*(9 zxq(Cz2mjov`6xc{5CyGG{77mH%?vc7{n^C(f-KEHPuAPvDulEI~Hux{4$-RnQscwa-g3EN?8?Xy2^)aC=5 zCvrS13_4OHXZOlpLE$ z(A&JYiu<{bQ=Bi#Am&J1UySGR8SB|f$}UVyLt(d|IW9d~lA8RBNqDW zV#+O9JP{D(tQ{R*TsdW`A*K&sma3;=;)Sp}@m@WZ9Uo0qiZ)y*$3b3r+)SU0h}oMA zFPDS?hZ-?$Zm=a~<^qakP)8RTawK%z=yssks~zDtBfL+h-oa6nZ_WanPwVsoasmCb*?H~PSz~zRvo+UWRLkt z8iH$o;rIy=ifyMzv(S;BA*Y!0A=nD6dj}rs4bMixJ;Qlqj>o9-p7VK8iLDq2tWf-j zsf9b`W7h4aQ80d1*3b}?j~u~D9%CfOV3BrDS`0?l$s=--2G(J; z;IzwLHmP+!#i)lbcg%W6YiBuY&^ee^sG-mPxX*2e@0FE&>6}kBrF_e|2VJxui)b{I zY_B{6lR)17&>*qZnE0W2uf+?w0oqAlwqQ0i>W-D2W<)VY?_$44aG+pVVfvYM`dQX?tFSab1g)l@1N(KURU3cZ>eb9`L%;soGw4$-Z6o354u)9j`QjD zc@VAGKbzCbC!|VK?|oRH#dw7qPqBzUHQ=)dfv$7XTF*Cvui{>6GkVm^9&$`ZjJYvi zgYa>xXixI?=x~kvhXjjNK7#Zjd5Syz&-+@H#O+BcyN7z(drMPb7cf>Fns7PSfc# z`MW7gOiA9h;b>tw-IDyX%oE;cV=yq`pD5dc-W^3CPz z^Rfv_`PGri^DmFgYW@Uq3KGF1Sm@?pvzg6$NJGI)0$(Fy^EBLt6QUA;cprYEbyu%v z6I9&yXW^+yxV9htQ7Q%2Qw&lK z(3J77FT5HvkLQE=Si$OfcyA+yAPmMYFIxQlq*kmhgDN#+hS&WaXxA=$qVdC>k9bz0 zsf&mqE{zND5U(E5DqY+WxT`6XTc*ZOy%3zf(-nX)!y~#z9?IAuY9;mF5J^s!qpN{D+3L5~VioG6KNvX5TkYlbB|ESyUN`ZyzYpF3>l^E4( ze)$jFMXHnKnsn|9jB->_q4Wvk)VtEpPnLQ2ro>;whQ5@S&3Zj=LzDHHSEI@?42rct z^p)0}t_kx)gFHA)rx4=1*G)(sz=P@QSAn4F3CVm}vpZSCKVH z>cpjPJ0ve(jIbyEeAf~J{ZA!*RlSO!kgr{8YHO$fMT{|Ak)-%fr|K$ossUyRj{&K% zo66^g^EqSy;-$*eJcG5g(7tGcMMynEUuwbRA zO*H0aZyUQsCXLs_HZf%z`x(Oh42LL{xU1u?y;ILI37`E-+wm+-3ALO;C~%tiS86fo zh0Pu}G_*@WwR3(wdi6Bj9y&*gy_n=4%?mW7rpNF4g!1ceJ2}2OIK8eFUBK$lMVVrA z(f#(hk<4dALfWTqv4Sg%8GiXjUxLlm3YKS(U?Mhdnadq$xg^Cq$LU+8nmQLu)Jppn z&pD=X2e~&3t%CyiE@K5_!Zs>RG;R^7YC2 zty$pm(t#K2r<*pN|@p`iuJ;)!)mrMsNRaO(`3|T6gPx53|&rWs0O>?(G)d{Tm&g?lcAW84wM zwkV|+JjdB7bP$>3kFcWnnHDcaK!`^YmYdWCi>e@aKP_>n98vbo*)zH!R&~8k(zSY!3HO074?*K1;KFC2Vko=N=AWkYZyM`EKC%n}=~SEpbj*~Q+fxQQuY3J3*7W!V z+8q-)G3KW6W(+3#9QWB3J~Kv7lxC_b3CSyMO2USUrL+2`iZ$8Q{}?oSB-u#wJy87Y zs(Y_eHZv6e?e6i#irRjX!A_Ab#%cW#xMM@PgykmOG^q+ z?Hf?mea!wt`yTsf=WhpO`^i*i zU2m8fW$4#BFMFqKi6EL^O81~~JJ8g6_&87D01?<`5co2KV^Y?UztOzl%1`QDgEW)t-*kuNuK+G=-uBGz>%zl3Oj0*fb>093Eu1WsJAm6ig>1A9f zBH`MM0OVI57MlERx|K7yYh^Fk;>)zZGyy1)zsQu?3xC8*-)5bB+zb;kziLfLyT$8; z|0COGPC>xdMrk#3N>pIpDvRU~{8Ty8>XWr}GD!3fc81-&0z+6ixGy2DSy?NG2GI_8 z$5r(-=#!m@30RSEd_ zazgV_>HH!ZTbHi!1@^MgMHn+nn0)ZwtG!xWPs1)2ZbDubAxYjX{>x?`JZAFOpq6v$z(uVc zn^*oBvkwM~B%=t+5U;H>RRPAkerHXf(dx$w^LpCYNLvbAQhdBwqu10i1>Z^tKm+T{hJd26x0@-wT=S+)_T5%`;fd()NYfY%ituXkA?J2Ote%VG5cN;52){>V+~=vQ#|tnVDs6(~*cCHJALLK86PN7O z`J{6!`-~Mn-5$+I>?lU6rg=-{Fe$ro3w0}zXXxqx^Or7%qjy_|)W^4%lX9bxLEkkh zq|Q0=8AZNN8D=cwCSoT=s$YRt^>OfI_G?()dgJY$2~Y`XyfRf&C4akT==0U%>S|NC zhvPVQlZIDg*R+Q5>{#Pi^xE`c9IAP_va66i#{wn(qJETUGE}wp{U82mb!mW>DmnWg z)+5aPsH0(r>j}uzxv8^Jv+$I-REhzQ>k3DLc{6H*zT~Kwl@#DQpHwU`-`*>5%7&mG;UsYe8UFfcHMwQ;=PhxX~;DHGey+Q^NjXdYR$?_zU6X}l&6S`ZeIGBuLUz=D03l3(lMp^ zl@E#W&Y1^4D4mG9Xb;Slr?{pqvb&#ZNWv)ybAbt2P{r-H_0M11`R+AJWg9sy$2|M%4crjlO9>0B=b+Px zgCU`oU%+y|=T9d(dn&DBs_p(x!}tpG-o}9#FQI4O1^8l6*eXWz6EkeY>ujOaZ*1rq zSy4*Z<@9eh(^C>2hhH46PC-~-52&gwY-nivdNT491@EJ#C0n*~OhqArDU=Z+Dngcy z$?lRTkcVDC{IElu7+1|h6ODv!tp3ig;0dkf83*6FJWlSD9)r{S7uKg8nwq>H6yAlq zuw0-Yc}Gc3?|1t zX~gxSLE&XXglNe4yr~MXJ?|0~c+P6nf0r3x7|+allOtsEVZvJPej!rjtfKF23Ep!i z&l8+>0+cCy!aB>o&4AFpr)=u2&(ml8TX(=f?K4CXOfxqBpVnwhAn{K&+MdezZ5l|w zW_%^FxW~)4>-g7ben89FathBfrkvqnhv3Q9kfB zN*QO094a^Q*F+lqlG2<1m<=it3CkE2lWa@2>`q2i4w|f_HMH*q-p9t|FWO%9{w{0w zgALBG7omA6@lxo1b$^ibV17<02Md^xMjtyFqR9^o+&C`g!MDNrA>GiU)=P^_<^FmG zK|2X&^=SY1c3~)}qw|+q+CiuCv~NwHiChKZRA%*hP{}!P?x6)*jQPXS;m{E|F$Z21z=cwxkb&9ofqKa=@Vu=*KhNjtI6Bfp+$6;i z6Tf#YmjC|Or*a@3dA{zDX7wz_F|aYP_4dMI8@-(`P)?tJ_Gt7ebyawOZbemMJ<7eK zeL15kuyuEXiX{Nk*naeIrqmVk4Xdi{QsO4|X3uuvJU!@i`td$y^L&hkkV>FA}MG}uncNa=or8W zQ|+pe?6_mP`g7z7xBOHJoaIsu<+e4JRND3k2F-?N@p0`XSRZ|ynRaPm;B=X1F%g4F z=RZ$ol{JcTSZ>m5ovZ2g-+jA|>-QuO9K&1loUj573+uA4$Lbt&=f(1Z^D2~N6vlvC zZ*Zv7!Z^t(c9Sav@3$ROb9J(LU{aS1ixp9Qr`WB3woi!t7QJotQY)}hq*Be_{ythi zzymxIM0pv7Z1cD4JHd-wSkB1!vG>__yi|6Q&{f`S&{I6WVvr;^VZ!9fmVjKGZ+Xg_ zN#&}5P#fvUy(e~)=AKipFUQsTJ@8pmWLhfz)ml`iP$3$}cxM%zk*rvlXW*DQ4XfJH zetwaxgcscR7j&#SWEPG|!AY6wClDM*$f%TZgi(4Y7qauIpEzCut3RUj*_T!3GGzoa zzCmwmY5yOX46i^iduS@3y^Ld$g$$Q2=$~1u1j6YP(LVe)#6vQ1S{TfGWpJx51gCvL z8~So%%v+kUZI8Mq4|@o&{(Sh0*YN7FO(JHbgS_jIP~ zJv&0@2N9B=*>-Fh-UUG4X;X`&=U}A}Tm|CKU$M2(nk>}y1#KPamFO;UeoO?HrUZ|% z2x5m28(C605EB-9Za&FS8w-Jd7V9_VMc|_^EOKBJ#D8)t z9CXtT?>*nwVb_t?zVjxmeAki`6?rtV7+~5o5nu>Bn1Cdy5Lg27kg*MrHT8k{cN`_tkgZ4A{DF=8LqF6u7^J2KEWr*P=fBI7DXL?N#qLNpD-KyB@yvSK;=cyUBP2 zUEeup)CklpL77;RTR`Z_=vMlBi?*P{t1e{hTYH*u@iJ{SD4%&@efT)=&B3WC}q}aeGbr=uCT9 z#PO~2BxOcuvUs>;JSs~u=KJly=R`!Cdl!kfd&H(g@7_@#)GSZ7g^IkerkNL*xBoI- zu{J9B({=YZ?R2-@!nx@7;!hyzVfk8w)W4LAeN*sTvGRn{` ztcIIxze?J=d&$#TaNxVJ=kedOgLBhH&qU@^hP5RNCDIR$ANybAJebu8tA-YH99sgo z`50tMi>uIV9HdCTy(8fp$K9$_lMrTz4}0g>nY+-SO@`PRs99lW)^;OzY8qKeX*vR^ zW|{G8E1!Be7T*6Dn;c8Jj-RRnYgRBnfu21C3dPob2mC%gab>FQ@Q|52Tr!@v_!7F8 zS4{c3Zi~E+VI4_-gS?fFM$-$&0Cm%ej4i-+<-~K`Z^v3rZi?q7|@9%nUB%VT0;WQ^o88(ph!cnuB8To zKp*bbPM`@QfmDVoNGw(CM%Pj=9k^y&PszdUfc)sl{+KrAhArng}zM`b|oK>|j zYXozqzUL0A!(wB|MzF22SGR+ST2J6zskG;fp-~PK9>JP=J2XDW6cP=&tG;qB@4%QL z0(au%s?(mfCE^H!DtMomc%OcB?D$-xpM?I9W`@$_6R#B+xa5)EnT5xHU>*MTWhh48 z8SkCkbB~pdI{oC0duIIl=3C$vuYjUT+=MVGVwniM1KDb4q@$U2@B`B$=(8=7of~7R z<6p^Vyqb?+gU1srG_|k5B~i2S#ieu_qicLhP+pi6u~)zxKj130b?@zx?l7g>;mx!r zb^y|0yykQ7EQr!gE~4oh2xXN4Z9<-T6W7a6rJ4~`{9x4c(X-X*|-BeKv5$k-M(p~iTuxQ zInmG)vY^iegk5d41xf-+b~(>y>3*LYzGfCTqZi&p@(xmF@wCLqA&eZsf!C;4vut!* z>%2&n(#fR^BhKcH{mknYeTg|LeDT?}i3Vc~da|SErT{{rt6u=IE}4TbzcssZo8;Q1 zyK=j16_xH_@1H+E(rjZtpspGG9bOpy*q>3k{_}4}#yypw#|aH@VIxS1MGE!!mOT+S zgMT>Fbki#qe*gb0z~9`z8-oYs6Y~kY&Q)u8cJ#a>oB^e-Et1=l3g4u!@LVPf2Mf2h z){NsH2nxkt_J7VjCp1R*2L}|qy^?wka-j*&h!}d`qxB^Dnj@R%;W8pUS-rb~LLgrG1c0PXnV&f~I zz%vFr2E(uZ!#|((f4_e*v-f^D`F`~G*^d!4u8yewiR#N>^A_48h=_`quOz3&jAf?O z#PZ&(JXjc4^h6ph4Hl&-)v97T{;T?r@57f?%l98zyh2frJ;EE?UOJ8Za+k<|wc9LY zunxBy-?9dvu7B+Gdpvu0>Hk1HB-s1b)o2-cFA@VVup9MW_#bvsp4F&i{{?N{i5J*+ zrPRMqc>-ktYLnRF-%m?#sRpQT8@Ep?w*rIDgBQ30huE@P*SP3ahIMzP zm0Y;jRkir47n0O-M^D0q){49u)Wo+eB-Sm(m#`@0Yo|OW?U6=BxjJ#=Zo#ZUuv>fE zHD#t=b_}|GO6OLJMy+&ks#&zdPSG!PNwdmZyCi4CG)s!8o#|H#zx`9V{PP_S)fOuX z9EcG4Jc+10yJ9d8t(}|J{e4M=)D2D6ooe^oYRNcN!NL{5AY{L(1XvGu3PK-@zDqOL z!G4G{&uxP$FEKL7(ySx-R&$jnE_{f3Kq=)xyO5kI4}{d(R8Cd z1OJ)bY#5j@pZB=^c820GbWfHi*~7YSNN_cw0xkhqzBM$~u>di(!ln|7Bih~N8Fs05 z7*2IuRB5Hrx$-lG9Npc-i?P&FqSTqYFHVHXUmqDD30brjUr)fy$=g1tR+f6Z4J~iR znHQI#7>WW$VZ{67G}+%JpOwA~Mo-bT;|w9@f!3c+>E6nzrqCi0^}`WGZARUcnL**H zQqflOVYHXD{c$Sc(eruPf-n|dA11eoQv&R`&}K9$vKPn0|5WSJJKKQAFTQ(cl`*$q zMgQ&FSki*asel-SolDnq`cta-wO>fIMTQn--Os-!hFud^-ZF3fV>*WH_Wc>lleFMW z_|%9n7ZiLtbrD?8lQcC(4Vb0u8=c=9?mLeH%byAF!OOMMXYw%kP}VGH=e0JYYhB=_ zrsV3)hfW`0oRi-~h;=O9jD8;oV^Gi+I2>8;7+y2;-TJvWE9cCuHW{;1;As&8LKNLQ zNmq}hgsJuo$e-taB<_cYz2xZ}-!fKD^=I&^*1jnw2p>G7w%gi12PAraChMNx)UJMi zpX2(!ii!{PoIQ=5Sd&u-Iv4%)Rjy09lY{$q_=C<4}ks(&t}{8xqmi3K+6Y6nmP zU*{ik|5{JKjOx1H**v=nOqyQXB8q6XRB1xK;@m55nhTc~<`Oz;TR5W!1S*Sj_x z)^@ntgp5J2xdwX<4LF%-=tH$PH>=i-a}FqD&CGc%Z>G(&V0po$Hbk=V%qnV6JME}0 zpYC-#PGN0)B_0xHM_GZFQIb12Xu!F6-M^RY1ck?dC}dXQ74!ygE$L=0(MvzEBPS7= z3}-F$W`9H1tb_0eAC%d*#pG)=)kzBlEm3|cnMNH2J-Li?(-O9}@U#knbm9@!dcKxb z?95O`@N*lX5wkkvv{Nn7M)~GD(@vx++etVf$G&hOKBaqnl6Cxi(;?~JBm$;tQZgxVi_jh2{HmFec*dD*e*9^GeJG3*H*3ki8et6LHM4Nb^KPyl)I?J=Pth&7MRvTB&bHqbfw?c@oEw}rGfcs;A zC{YbjX=6nQ9O@FO4jzQvJ3#O`t|@320DpLnXLXq3u)J>0gpH@TJYasaf7`AA{y?US zHNj(-H9kAf!5;R?-X|TxD31Y{Dg!FV!2J|qIVGo%vdzck@e1gu)M@Ehpay%a-D`Au zzuDzPz7k)xxOBo_yEy)jViZg&i!9pEIIIs{W5;xiUWu!DfDSWo+7DlSZ=yzhju`NV zT1>?l65pr#JZu!hKa}vYQm_#O16<~-ZZ9!>2mh3@+r7q~zdLvL8$jWgQF|y(Ut_+U zxL*+Or|~SGqs6q5Cy-Kb?^4C}wWEBOYf~YkY#u+HSuxOki)O9}*ACpk-v0s|h*$TW zK38Dfnq*L3dnhS+{2_#$f!t~S$>1ptSe%0j(s`s`2#v0^2EA`{Y04v?;?e}J89w9A zs8W8wrV|gYwEWLKK~>1hIDS*k*Q60;VP-zRkK5v^u8A~UK=@pWaVdN6wtT2-!VAu= zk#6rsI9L`cUqT41!pcOVZVB#^elfF&ry?x}_wS03x^7jws{768ApWY?@hI&ZiE?X@ zkuZKO@k6s4ZBJQa@H5gIo6KcQTv~V`T(1F($YHq!zk8QL<3}q7Bb#?{$b=j@;THvs z7Y8yr>OpgvMjAY`ViA=1u`f4GAq1hso?1hnOTR{?TOT`0E*kIaR2y-qTjm~~phpEB z$hjys86W%_(O5-fyw>{9r@JtyF(cb-9p8X?kM3C{T&VL_#x#6_46%G?!X2=0Y7`*6 z)q!MUwgvm(UX>lfcg+e#)MH}lHfOVa>b0@9Nra-~2(6J&b2CI^ht zg+5)qVQU|)Rb8-N-BG3(pD6hY@Vc3~GY<0@OSQ398XA1e_h3afC(5sxUPH=GpAM|% z96h#Z)=hdwML>6i47R0 zb8-+Y!QNJcgUR1HBy0=;ReZUW^ICyM@+*;w_=bhV+%(0Z(7f&2d24uNjKKA835X9a zj>vw1)4M!nCVCw_2Dk_hORNtZkv(6M$cc|j{aZL52oV*HSuuT72KZ;MRjlyC_nLQ3 znNL+#Wj>DB=Tak(D}TCNtJqQOLA8{PqBx|z(Y!_x7nRt@bLSmSbSKfl0Vaxg3lr@b zq69+DHJd{6rX&JPoQ3#9t6EJ6cBA_;2m@&>W5_Ja+V?9Dw|CLU5uRCEbhkEXtJqlG zr|80%@xVE%safCd4^JT<#?KiRwh=z4j$_h(MyYn4jU6gFr3!!Z^YSa@VWHFoH37{$ zO%K|R4c(xNoXj0wiM)J0Yp#S88l#wGIY>E0 z_71bZ*-@9{VC15o(cL-zH=;_53rdeDu^QXIQo7;qYYc}+I29=<9pbO4wBPYg_6?qp$;*7RFUJpQ zgh2-MRybC;J2+FqPDwiGmSW)m)3=Rv2R=`I^Ym~|6~cWp)@Tyr+C*3PYO|{|107G5 zs<%gJYkQ_2e%KHFu12~HI>Po{zP?`HdJ!QA3cUWajyp!Bm^)Dd37CScK+Jy8-}c1V{8~%p+jk zxw6d5G5P#C5;YDcw#=ryJqAI2->>o$e8`o49FTN)G$Yupr%yGRdjab(^D$`Gz_{?bej7?3TU7fnU`YUq zOPeht+TAyLSDipB#B68i{O~lmu&$O&JF4w2cNf?M+n8Zo-NX z?1Q+P@?~iI56NiB@r5#gd*!dX1lvSBgBmdk@Cap^y}6BiQAvcg0B{)iJ>|iK01cw| z`XCOt;nSF)XTRo$OrzP*)T1xJihep!S~DvI-qmMNbBqbBnWu@w;q7pNjZAqB#v;aR zQBZ_{JpY42xT`uF+C(75mUz{_CK;0Nz*RB&6{0REt3%*cN_lHL4>-iv4iY3oNUDs=F1h z__S>VmhhZ;CEyEPV#`KSS>lZNV@x&Mgp1#R@78(5Y-Zh2B%9~FN(64{7ijj!%*JSV zX}3+Oi?@q{8iqZ}cvN!nRYccZgvCO>z!rv&7LXH&KH~xn=li@OASV}Gl~|8h>>e}?NLjiD1z`rTb@+I(QZmUh@rm@_ zx!}IPz^aTHDjpktbX_z;-`@G4OkgoEWbugwk>k8UCx-B29qdq`JG=e8jgntwyt8E z3?5BV{93*_Zt;7C$ocU@)4>0@6Xs9UKUR^GoQ?k+XFPz+CjuX2nN#Gta(?tQ{tzVX z(Irs)75IB>c>P=D^+k|cKU66FV%SO}_NKSNm^z*bDLl?qe8X|eY%ZzOkbgB$zo0jz z$+anySGHg+WS;FIEC_~4ev_fVuixeR{Ifa>X=vtS59JA@PSrq~e8gkOuCeIKIJ0+7 zkw2SS4MMM1j4Oc?$ZtUWbu4LMsq7ZH_|1t72JM2G&Lp|9Q>dw1T8GaiX20*&wr+$2 z*e>mMka1hGh-4XgfX(p+Z3hy6LK}Yt27KkwLO#ox$$n`0&#bgw1N6Wuj3qp3k9^s;;R=@FV+CC|a7IoL9XjNflozw)`0pSdRMIJk(#@=nLN` zqZXcxD4^Oubkxw@64K0lzo%jwd$|l=93LE}>I4OGVGP>KL7IRb= zwese;)-Ppy%6@Ds`G@R1!;k$UUfg0@-8t2UXgGDtkIC58p-nSg%EuP+JG3&qDCABf zQjfAR*f3)bfaFTsi9qZq4sD7#XIZJ=t$G1$pg<2&j|Sy-b7AT#wJ)~>(DNMT#fC+) z6pR9UJqNHJRvX(7F}S~3!>AtWDu%vYw{ez#yL?aGDTKZqub)`YP&tcSlZ(VL3y0-O zR)OF~A>(jX4^q*9GC zp(F3jjv;ZSGj7e658a(gIw?=u^6eBwwJgPqKKZO!OB{7j z{vsli98sP6ZL=`3wz!Z$!EH%WW0rNxyn?%I@p~J?#EIOAhq97DstnFKg5pUU zMX7p0;pOFdfZ|3u3A1S&AOX8iQQ&N;w@4Yu8eaQe@#hq;FH$AV_I0IfPh| zw`q1&jf5gEzIi*|T*6LZaS@310hgBy$77i}3(q48Wq&eUr=M1VH9nR=$Jia`-CG78 z^KhU_tkcyk>*aiuD#Y#Ncm<_iLcg2=J@Tj{wpEqlT8jr?o?KK%a##7fQL_;QqL#HkG*r>sF#WbAJRNVKRGIf;?=!IS6*hTJ~dRGTpme z0G+xu=OtVQc6BSF(lj0ISsC$rUdGfI@^!sar`!a&W~5hLoHZ?CSL#Q(>t#<6Ow_C7;o$12z3wF>FpT;n^u+Hc)gp4tlkT-9QcD2d-D$dd zj8FS!*GO8)?dTVR03CIp@`UjU! z2k%Run&?0vkep&k#t*#_Sbgfi^T8ZUb$GGjMjp5=0@;;)QvB$)UKsmT3}ZB^$&u}F zF$LswO)Jg=mLISdRsEtqeaSVNlwv?5IQH|x9`a7ij5&r_(fk2SaedN4{De>JtkZFr zyxOxxBjG9MP*l9wb9n9|U}18$<$b%=|K{riEMt6SXoWsTYCXyayWI=#(j z-K}O5HO-zog_}EEg7fmyFaUC)4E+y2%!=!?E z9fmfR?a;~`9e572PSX^Uudqm=gxE3ZSx4doUkOKrg3xQkTqfeRZ4|apw^0R+)}nss z*tWLZmX+MViU84hzM1nZ|5~UfZRA*+QaucC4eyn2^g&|_0hPF2XlE}4%CM49H%a9L zxvz00+^f#`t)+c;n`C((7)yJoMNgjfA!UC^6(DW5%~H|lP*t*Wq!=or3wi&0^Lfy- z28LI_DHZNEwJ@kUW;5YKNjaXDocIrJAk=!8ujcy6&8vt&euJRT!XbTpcK=mESUUBu z_UP-7K1Ye+_T(LMIdD_d8)*u>b{~xfY_(VmH1W7_&1k!KGFwHxMk6ziJVI}BRr#ZD zVfCwm^5EO5X9E^3qLAcRG+F$J0!2plDzZuc?c^odU@7acDoqS0vY&hvF{w6omRn}m z(FJmH&KzL`^&D@X0O1@M6mt9OsH+wsrys=p$1HKDkfuA8cT-hV(2IJn=&D@|Dqc2G=;l1k_o~{X3KCG zeh31S5FAYS1>+W+d5%ne)5*pti*+CKl7#V@dGFoDMZfwJ$We27jR%WgzX=(Q_cmj} z$u=8Q+73_9ePLBM_3Ls1ykJ#I2N;-KF$+I;M;9g_NrEi(vYr*~%Kh=5Zo5c$Iew&0 zC0wuCWJ)hQqJR5|@Bu{VgFC857{szwR0bYhzi;zM6^zqdo_bR(()VeiDd$zMK?9h$ zqRw_={J!!EESLZKUhwA`ATO~Ws6q--Wax3iDP0YF^$3bqk4eF>G1Sn>uxY5fp zRIegDn;DZ6kn<^CbptUadVBmg!#1pGRm;F2@@L#zIdUQl8&Ls|;9b>z6fv!{* zMtMCtJt5unv#^}`P;Iy@a96b$v&>Ms42viioz; zZ%#$!#w!H!w2@9N?H#u1E;_%hEK)?S622x5q>Dx^{1-v>i`?*u61R@%;q*RFz=xyj zgEidD+ErERg~zq%C8t_CY2MR(Nw_oW#z6*C*^#yS+GkW%W5(UWAD{qIOR1(0Z~*^A*ZYW##O?@ zcZXi)qE8Tz4ne!n#7Bz=32|FQu| zftF8Ro4SYLz72w#H$e|CGl^HfbT1w62LgY408`G35c^uiZ3gps3h~hO1}>qmU$NSN zKXi-cRP5lpG8s28HR9aT zzuf_L#v-`S>jkri?mF-*g|rgungp@x4^$1W!N(~eN8;ciscQQs5r%YXHR3>gXS~%G zkPdW*Ap9F-U9f5vxzlt|@ZsRQ;KPPNsX?qL;U##quaL}pbCzI?_WxOc4L11$u_d`` z|9VA;9k&^Bc$$YXIrj*Ieu=?1g-s!PKXlywd{^#KY0?crQZJ1dp#33f6%7t&eJC}SMK(Xqs93Bee)?jZpyq=+WKw2OtyFGIwvb`?tDP?Y zHTWPp(=LmAp#kBQ$O329NrSZ#3oqc1t*|#6uIBq-d`cKkHPCT@rAl?x-GFUkl;_Hu zT!C2QzJr~nwx^1*LJE+Q!@dcMNdU0tQOBSg z%dPVw$a;qJGlFF%Rdk!B%5$9n6t2ivS8@)=97Z*O)Cri_@f1LD+6pAaXM=keJX*ec zb<^I)PX*S*Jv43kVCP}fkG-!*^B!GT6i27=w4kmP@F}dF&r@sm^t4aU+Ym>@C3-*je z%G6Y9)JLre9kiILUA~>lpGUl^%JAg6(TqMj4~Z?CQ$jj$Rjsk(%kO(QgmBhWzYrjy zPnTE$w{rvebbaY97jlg!P>=JlxW0L;tkM}1T63}Idr#~}gWe2)LzH)jm zw4LkLM$2hY!K{*I*zGGx^sR$aPNJC5P?Xt1yA3P0AKC*5P2+5bgX9=XN)zKly;T0Gl#3dj`nccquv%d|aG$mzbgd^Ym9MBfYzN5lo@6yG`lWd&b}7`BRt z8$8hyh*+;zW(B*_RJ($_am9Q-{CZ^j4Q8#BL*yfPNcaN)MHdr-q#t~U?zB{J`-88J zQc%5ajd>Lorz$-D@=cD%b`7l+_nE=n?Y>FrUdF zBX48Fu7iF!K^gR8k2#=y?VYr!KKxx(Lfn~!i`IdcYoyP1l;!aRvCKPH5Tw^mQ9=e!Qfu2LyJ#4wtdQg1$e2%rZFXyPFGM)D^0(qVuzz?1-f}!9$O>4wOuP$d}*gp zE&ztvS9Y1l@mcRH;%3_7h@Wb|=hLMTWUzO$I>Pc_nZho3=!PyxQftH;Fy^= z&bXFECH$Ge22400X0_OT%rP1!;LN2Ni%oUqep~3uZTf70xI=vu{wW-YU_0ZX$^5d3 zWwnrf9g8->Grm*uyB%&pMfL?o#O5%lJjeSIur%5U(cJRN;q)?^@vpNrKt@3i7c^2i z$kXJ~hHUK;k(p%3$+p-5K8E$zGP|H-zpO{)&OIwG51Ql9jpa1A@-JSP+p|hm=z)jr z1&2-fZb)I2P-hV`FA|NBpeZqd)>-6aFL%( z7ritP=F!D$h$bv%6Mnq^{Co-{6?Dk3Q@;ZnMq;2h7n|@a>jN)(aEJmLCd}lShM0g3 zJfylun92$S7y+={g|IvZ=~V^T|LleDNW9hOThm6I9-t1XpI4>REjSZX$Zx>R?~BId ztYK&(OcWb~p)QJv?v{--Iyi>=m($C$8DWkaR0-m zQ{QLbo$?`hlgr6pvfp(x0XunBruR`DT-_){%@HTio>#axtYMyt$hbF2e5StKu zkUod}DGY?A3Un2a=|SRGKlB#n^-h5C*r_JLc;ivo+em?3WWTWg;Jn>YdIJ4o4F@&@ zxiUSs&i9if)7pr)^@wzt_J4GqbjtObQeyYnG$2Ij*_Dag!;G@jLFG8f6Q&_snPwOs z1=%FTfzcQxtM5$fejn6Ge`kTyaiB(3V462AH)&TGP3t}5YV_L`is;93!mSyl&+`XU zG{AF~lZFjzrVR&+>L>#azdVXFox=H9a?3H(9fG`NA}`r%!~DuH3CNz5yx49>YsFX$ zo%O%2)TIXC=>WkPha6S;uxb0Oo$toem;%aG>KyEg>?;e|%ykaKOJYvN0HLcD7#^XA zfMqn#sY}+d3+VLKv!K>-HfFZ)5^uZ(86GWdRl;d&q1|QprRrLdVKwIDIee-LkB-9R z>(Zos$srZ}U61lPTnT6#Q}eT#5?faUKn?K<@H1eFvvLXUtel-V1`_zPl5?! zmFF;j0bVd^Em&6(3!w!FqV(*wy>p;JAwO-lz%U2!ZzE0g0Ozq#3?t>rlZ6>=$hW16 z!o73o43RTm!C`xGl-pqcth4Ywe?eVJ?&0tnV2L>?if%gZF+P1ZSr_x50mULcOv69J zju~?26K~Bn{+t#CMPdOA;kHx{>Ly7K8js#+?;yYglSx}gE--C$r<+HafNM5k+u7_D31udZU zmL~Rdp5O`2l?vF6O8C$5na9|bTmVfRJx(vfIE{w|om2KD0JPoF5~a>R*TZ5!RCw0v zXUij1;W)Ahb1&7UqL$4ng4j3Vt{-sFy<4>O31Bx-epNm{518~kQDCSG7p(C*^!%iJuEujwgO+eBK4v2b)vQ8v-uca3U$bz^gK z8Khl)4PA6VmGvyKOg?6?Gts61P`erE8XA2TO_p-BvWAd8y={?v98`@)6g%pSLy8F? zf-Wgzke#PxQrTJOXIWUH-aY|4)DQaziA|W~{lknP@TePgfb}-^rlI2%x_*3MS^$+U zUw+*D{Ex|Kz8kl2cG~%stio-T=zp5;Kl<5umNw@Va!oA6nLfGcgtX zH<&})`ueWnmH)Mg=#(3x z3oep2?3dD4-GB)QF$>&SM2Ezg?O`l&^H2dWt2P{ci$)YA{C3|WahLdO8U<`5DllSH zWR29$%a)yUgx z>MDce#5eFA8O2T*Ee?0%6}_bGTEx?0XAO&NB(Ln2B{@+}N$h)0s`y;|y*`24um!J< zDSyK-NvD#Fj{M`M{7A~p)7DRiuOph40&8f`oQS<~brpaH2aH0*%TgDN$<$_QXX&h> zZAoK$DbkTRFFW3XL;RZUz=4-b8g?|!ZOIY|R-f3`J|QYtaX|iqlRQ7q|i@o@3B|KqxeK{ZN0$y8#n2| zcYc>Nm*~qRR8XpD%=s=^;1HM>nPe%?Uf;R6yq4K*#Ztey>SaC&zvEQRIx6pnO9ULS z&u%+%RDsv*eA4G-o++6T=9%rg>;cE3U`Vp6iY)$pkPk1|BN*GQC?P)_r~~9Hv7#B7 zgkomw)L^h!r&~(Ney5ROLy?w^3Qrq@4nLL%nJU&4)msal5|0E$AG!n2cKmk>(X-fj z!8fo=aDU)d(Yzq@D|`RH_xFRAYE;43ia-P3_0LBc{!YOj>i(L3cvnAE?4Qt+F=IjP z(#jUZ|K?A?H|m$W?|NIkM}F$X-M2Xi#X<~#{&qhV_5^xmDC|OpRxOgUh)E#K-c%K zEB0aaa@2!X<^JLgRHWYoANuaWNa?Q_U5Oc7(<|z-%IkC-mTUOohR#K*BvmVD$KLa? z;z|Jt0_ybusVKd7e4Cnn`!oGEY&YT0ZhiiXQO`35FkiX7+cMJ(+}gfh6wJ=)&A{Cf zQK>R4WX~YqJ-M-yVApKis}V5mb>&6`2qannRp~hi6)ngC;SS{iTdFD^P?@$IN^rdd zw|I{riR0TC3M>sNZp6&ZQU_csLmvk9>WzpuZXVbxVsuY5oaz=E7cKI>XnL-t*8PQ> z)SGfHJcjI<={;cp6?k>O_o`w1T-}yq+rhSMTFR9-ElFYKh6!Pt^!%|4IG4#4{9xpbKu%EXnT#62XP zT^q@D>=1WEC+^*>+zG@@Q=sg1WB3bkl9BDBaS>~dIjI87B8P99TB*9q4oQu_0#=kg z3?aay;_!{I3jdH`RX^QyoJsL#bydQ?x>YWgHn>u1fO-YgdTU$O1KR`m^PCEJZDdpR z)H#ci7D3G)OVpP^)c~e^4AC~*LCbi$uT8iRDC=9d@b6jpW7D}Om2``=ZJLDop=&<% zPS^5yy*=8eS%+}-KZ(;?v<>GfX9FhodM(+fc*$$jS)4&=)_=$nxk~gXr3tU0tDXq~ zDNnfyB~tXzzEPmC{*}tj*mIK$l@6TqKScmt@~~qjkp`?f zpBU#UgLalC2~&}WY1I)52LH>BH=g z_0HpomL(MQ!}SE0PF%Nh0reX~qKe>f6Hm2I8GM%AHg1}2-bhTcJ^25e!U!~o0Vmx1 zr#BG+i{hf9mEF1wL=S(c)c5!Ujv9nO^Xwnx3a~&hsYyrV(iBy z3K~o*v9T0$#oW*46;(ff41G&1qL)WGR6h(Cu(i53<4Z9o+17v1a{uf-539t_UdU&{Is#zl^>1h(U>Hf_YD*ra3C54BjWGg zD86qmzUAx}7(a0{05s?iCy$){a0`}|Hd==CY*#!S4#T&zW;*k7NWiC585X&e`=1xC zU)&Si_8W2blsmgCrW=^$4@;3gDinet5Y7?=v|g-DZ^6>D8<_@WVx%ixe(C zkn6seYc?$F`@ZeC_&og3IYr0lH2au$BTOV$ z1kxIP^0zJ!7BG(ifR3$cf>?mX)m}^kH!R*b1KhhKKnjpS z5@QP>!O}LU{g<}`RGt#@eMFKm42Ia*)wCSV3Wf=C>{V4fb=t$YXv2z>?ljl5b^fno z!{0wVQvIxnNs-QbbT>k(KMfS|-by^wQV= zRE|mH81zP4b1U7aea@Fyom;JS^19@)3X`*i6}@)Gw>?~xN%N1NgQPA14&BxA8&7F9 z%KzqO6MDxg7Ea;_*2Ba*Q^!S-U|%tHRP7s2HEZ8dEc7>Up#(OwWwM=WH6!IuZ_0vA z>UvT+fUFPZPCJE$*tmVkm4RZ4xwAUkF7%vA`vJtI@K{3fWs$iZXFsAHK9wp!x+Gqu zj`{{zP7Q*E1mw;k>1|LcxbIbw*lTiH+{~l5b-cZLSx+Wf$D*9qM!@qI2{D#Zy=e-& z>dwY31h(Kj9z^F8B1jL$p)Rvwgo~s*xwC0VMs}a_y`BTlq=a zikKnd3TP(UI)|qV#0MK^l_}3X-K%gx=J|4ZI7lW^680L$dVQp7il_pvs*O4}#^;vU zwk5G?Ug#9#l((&*VE`$6Ukq04hpSFKG+ElQQrfw>&GLl9fh#N|`vsKAC`tJ3s4Sh; z+rDAp(q@9^x0!uZZ`RQ&<^y;X7bV7Hc!?wxk9ciFvRO>iMVUt84~z2IMJ!GVL}&Rj z=64)KPapn5l?= zTKx`Vv--lQ0+QAl?V7gM#IrUf(Bwxm2wp8CWBkGUC-Mc6ObulMC}x}mExacrbFKm# zcB}KXdAm=Waea&61+Rs_Pp)AU0r*b^+_u^8tbV8*M4Yh;#m+6Rc)9}_VYc#2Wjq>Q zxPYsNnm{0qv~6I7j`f)zNwS$?o|@T1rusvF#3X(0>l^9sn$ax02(kJ?JR_L2MKp@h zUeTK9|H7U@8GxhJ@9W|!mg%;&t@>PU8AF87`}XFL7+|0~{u`KHZFWxGz5lPSY6|UR zY*UUB(?Fe4PjgQ|lfr+!q`FMbz{)lm)P3oK#I+5dJ`jBb#ofQ>+8RJ)N0ZURW^eld z*j*~Q2!Y|`ONK~mG;?M}?{WFN7#(3MA3aZAe!o+r>U(-yfHh+j;s^8tVA;J&$8%xp z05<1(jj=@%fIEh?0rbzKqC3I7SuI>IzfI5?@3JveB2MdqOLc8k{KL)CE!c&G(N(*w z4*3CYi#F&<78Le`EzzY}3m(+UI0lxX+!m9%wd6N_zEnd1#JW%KyvO7^WQ(@R1{aBH z#{_1fxYXCPuiY$(IERS4Ou*??7{s;9SlSR_(VCB37m#>w(0Lu0u`jnD{v&;w?$!|1 z8+me)HgcitiF;KGGKt2m>)cPbwS9LIYc(f+is^iLi++dY2@2+jImk;xcKLk~f?6c)}F;;e3UD zY`dNHMKfkOtM-n>UauCsA|zWOeCv=A+^pV{TYW{Xi1Er!mkk_J5gnmo&yLOM?qk>N zlM^Y+CAz@*rp$yz_DHQNEP~vHrWd`I=TF^qeeS;>d8E)k{A<0Znn|$!o@4t@ zr?x>k^>WY?!5|i6I^IT#FjbeWUN<(QqMMZ?N7*0uf)X(drH^SL;FI!2cH(9^i>X0H z&yP*d^B`Q@Naa-f#bc9^1j4K&1pQP-#w(Jo$Nt9YzyZyBJVkU3j5-AN-h+J?zt8J9 z*98v4H|Om4ML?+lZ&}~9p$t@j+I&JL(-m)uzQX>VrezD}q?3$qChUpLyH!+2XcGu~ zZXXXdL<&e|iKQBfcU5=EE4u#y;9BUo$Bj#54FWk09l4yJIY@&3L^QckeZMf)7kY=m zJ@Sb`AN|3$f7Ys3+p+8Gl`L3Yn(H1sKMAM6>D9L^IlImp6qG-Yj1yj?fx;R2(&x~^ z(=>kj8keZtpobPAY9fi9@2H8eGvyV3DD1!~gc8x?`9>^xp zRAEFn9>+DNN=Px5>nDB-JKgDEg-FQ zgHEJVx}_wfLApz6eedU-_y3Wz4;;pQuKT)vnW=tMW-6MBNhqxv2%FLBLYv!9b~CaS0GHhnWdRvUP z3G)LJR=urxR2gnyUl4?q&tZMIhoj3oZLJcq^OAFA|5xXWonO|vnwm}cy|sx?V!OP{ za--BH%5WJ`IM%1?Nq@!OA0a|z)Q?Ki#gN!@^lf6^7Aq~iYQ|1!_V zDYWKwpx~D$e-LGnTpn(L`~zFxK)~Y&4^hSW?}XA7uWn&u zKguOI)A4V~4GOy*;r>QCv;vao9c<~B#A5uf{qVt>kLllSpD zE=eQx$#_VV_h^^!4G048|1zj2ba|aajAP<|?_>L^LwtD_^6^X+jZK|^G@rknS+XL> zLL)lI##}{EDTnE&SD06t_kq$@X-@}&P^tn|i(mF4)h1|Jo1a`jXWl=a@a|L?EtnK* zd;xW>oHy(JK0QyC^!ETAW<4=xo#Qn9$oc(a#>a*~J3I~(^Icp`Ny8La6y`M9s=(Uk zdsF|@0^pr`JTI`wHvjaN1?~OC@M~bGrp3fOJ@8uK{uZ_{i_@?m(pdjFgKesoZaatj ze%v|BX>x4V?0jvRh}n5wB;$G0OeY{p{}`niq$g4i4NeW}|60@8W2`wZ)_CSNF?qCJ ze6M-(AfeO$ev183XK83`S^*2Ac4x%Nnv{f>DV^G@Y_(ShTRcy@f)^w5T%}lz zS@7X7btTd5U*B6EQyY$^XRD^~5v4;8xQ-BSSR@8MS|P1}tQ8n!7euPmF!_=$p{OZW z%nEmNmfZWsJ`}K_-F8LSzGZkmOi-nvN(hJ`Cmt?mKhN~HWH+cqm6twsvq}E-lN$Jd&uH>X{xQ6&D_><$BBn$ZnbN#n8 zYK8_qWwEH6&J5hx!Wa7w%zGcCX%smI!PB127v|*g&N(0M*JI(AI4!xAk5%q5747n0 zwT1+Wid5ci|W=ehktDB40K^P)?cUGCa7gQwdoD&qT7 zdQYN1@9X==`IPKo0`l((cfwptIXSmuCzrIcm<1k_3OhtGYR&LPFJ0`ZY3X3wPqF-l zzQi#{#z5akDh!8zn6Z5Lj+J4|;b;o$CES2SO z7tNrKFVzZ-^s*~$zuP+*R#w++luZ0w52A#J3G1SgfM9KP{rJnh4&RS=rO(>+gD&g4 zOhaA=U0I%l1g8~3$tFDb%gL%zg!+;&%*65N2yr?m+s^ja*P z=z~yJ!jw-)={($zM zR0~na3YW`De?FpT{n~Ezvr?j;<9(I6g||v(0j2oIIx+?}v%+j?jE57BEHHeN!zxK0fg>|cR?+SnriTraJzlL$Jw-K&J^DZ;5$?t>gPKwbouYXn@MJ$Mab8uGrs&vSK-!UCBGOvtS1sZ4NFWM* zQSmzwfkSGtF03h7<#<>tz2Wn7pQ1yeOzN3>rKqIJC@q ztSmJdbIqqiSv4uSSRK@?a_-r&^`h-v;25Kb&w^9alHJ3Deqs4b2PPrf<9AUeMEG6Gq^{f}g-YOlpp>B~O*U=Lj*+>t;Fwf}2u=1c;1D4g{iVgnbDI zPpI0aygT3Aj~d?BDg4rjy^dKzemhQ1?4Y4|`tn5k!#NrhbcfflX_0-+pWh?)Yx+$u zA6S=sb~*_4*dX-}bdM6;%Iy;KX#oMa9aC|#RkxSY1mFj7)`L=4vld^8hYXkJ9EWLB z=m$Vj>mj{FpOcCZ@$~d{$z+;XQSu9GX^I-Y>8>P0CrVeJU}R!u<)e6_3mA? zXl7Dhz85SO-wly!+=ty|m3B*Oleh=AK3Z@W@?6v(yFl8O)>-Bep*wNcB=f}!2_{)+ zA$5omir#vNF%{*@#|}Em^2JsFT2tHcm@0)#&@@#TYBRxdn1{a!YsQD*E8qdcl1*N5 zsH?lUvj|rWMOywrO-kKIZVnnoz_aM*^_TfMnA6y~D(#i?; z%L?NHJGb0W)hf$KBzk|tE_LBazVWpS7*-#Zlo3TzK)ix%90(|J;m5)rRNNUVb7!3q zDFdJe4P%(~8~6f69S|*QGB?DTFWxLQk9F+k6N4s_M1xWa02jd&!@;El!1*2VLRFdE&fewiYLhXMr>NfiIMR+Mz=)q=} z5mE6rz7~-tKL7i80jrBStV?H1Qhe5=5@>Z<$%`Rs zSP{8;+iGf9X==wm@>7kUdEH*<`wab7rDyDB*5=X!`)jea<{%WXc5+`nxdwf$>-0KS zrN?DA*78J(aY2)B73GJy`Hf2$FKS&*e6FlsO{|+(^>W`kww=C6fp{ys@5M^hs(XPss?FqSYK z^Xhr&^i5O-dH2Uz+LDQHLVc`Y**RSXFEWtA!2tjZOqBvqGV+sVz- zMWaE3DGx#>dp|hi?umcCfKGmgc+b7AhECpQ3J-HV3z2xr7*>O5vh5mVbigGZQ3_Wd z=3@>?l`(4(#CngKd5adLrR1$wZvp#$Fyfw{(ff$oP{n8360k; z*tAOG#Z|Sfau%(O>F~_Q#Zqe!KUtoC(0@)trnsAuA$YLL&QJ#Rzt*at0-;GOs4Q$$ zO$qE=Q~P4b=q^sX1+_}1W-1M74`1j*IEih%NGj}j>A|<>0`m9c8qpOj@`&>6tyqg% zqN=wynB-;rwE{sc_R7$YZ&0iB6q7H?zjg@nX=bsg(j@C5N}$|F2;xmdLK8lMDYcRv zX;DG0@pHW;qkB^NLJZ-fiMI9Rr=Js+F;nzlS06NJcdh2^0U1x(R*}? zFgc%je+xlr)pi~o$f##pIts;~2OG3=I>t|agy{VF_EPDqEMAXvGqtji4CKgO*O15v zzxTaX7T0`n5`6Vx(7G8@mvjmc{MIqxPQrbEHhWv&XQ@6seE1vsN#vQ2V=3v;^4jsr z>lK!1qW+4S6#`AH9ET@Ul*|e(EW8?d=Ns-2O;l{dl1a>f`JkNAivHu}9 zR+ofa4!*d>;8}aYv;^Sq2txU_ch;$0fmU7s6KQ?=EV=m2k_9UG)2LwOP86C1@>H01 z%b}JKzjpXGCSCex!7E-<`Vlvv`piXB3$LL7N>zjq4k=Z2%qcy8G<{d(+ipx4^ROq4 zE{u$FG-;AW7jM^-+b4_7Qj3^kydkp`^7r}URHDI!BXVU&UiH~M=2=>%`cC{~KN*46 z1xJ5Z&C5pQZS4&2A<+8deconxU)aVrF6gUyi5ZoPKCO>xnR09n(3EadIk#*018EYZ zKQ83Ru0OwZ`j1{+GJm6Ed`F6w{O*|`aRJO{#%jTLB<)FbboSGXN=fu z`C-$(Xwf=v*Q)1`QNh1hHt-v|tTP1a)Gf2kEZA%B^b+6E71URIhIurkiRtvr|LM8x z97cDO=gJ*-fR77iPy)k(SxF=pclFKy&9wVaKXc*>MPNr#Kk`jE zfIHUZoVfOfcjePK(aC}1j5xZx;GbQ4@K5u}If)V!<>rXrx8NrN>^+(&@gXhKscRIO zjQ=CZezZn>_z0iq3w0faLtGD*U*Cuq%ybw|&&CC5(7z|b)E7ccX^NLnR-ge{)l~SZ z?RG!oq8$y=+JAypDKsUp=yMBEZ71660l|(6fahd9b)Fj~X>k|lkb&c}Y`CY(FL7U* z$?T|=>vT5OEes3j!o1~3@yvxN8Wetpmko*RZ0paICGI;@i{Cop;-$0|zb#AsNjjRv z;2bp>sKwV?!ERR&_k%QD3^Y_ATd&{ogsOoJIVVQ~MI#C9#tF_?MiU3E1~L8+RdULC z@uDlt6D2#$0=JKflAAJ$*M2Hbh+web<>ScqP8u;#xr%Gqh_v38hDv^8XiMP_zUU$N z<7gWo2^=v(Egyk-P2}~W^UKSz9#jA39vNg3Jv(k50xg;LFeOR98`lnnks8VZE%ch< zt5LDZaAIaUiR)Z=jp$Wl0Pz|@&9o-Z6-Zovip37~;sSqth%*`;8lBvra`s^NUFL44 zt2F~kZ&g;4P=LN@j!)@&JdzQ$0mTkRp^0VR*(8Z!l9^sd44^x%1$OC;k5#>#4wi7MUxAUb`KoA%0u)wG+^DIPE&>knu=HAJtL<41*cF zkIeS>XR#5N@6NLR(YaiUmAWLU8uCw+e0Wv{)sq?Q$r%$hdR<#^_eMHEs#2oV-IN{_ zFP!=qUCOUlOP(=|_6kg=Nkd-|5KNMjx;OH_V zJH8RFKqYk^<>+Zn_>n?cz;SzL!;FYhnTzS^5aNl#4;n3`psjkgeaQsxE8u9$d%gGy z33KyG2}1q6E}-`Vf6a82TrrzDXOINGcw-yfvPsZse#`@p?c^-T`)fA{LUAXrRPCbH zX#H2gbsO9HZQWPho6_HXicd3=UlS-Vwy#V&@2$LNz0keDvz<(tBb`~QOse2 zTBVzxa5(1h+>*C(iH%e*4b?6OdZ8wWIcg`NC40fe&r{NNyh)}@Zbr^pcVDwzgy@e2 zX^(4ZjzvcrB+}?j+#4O7kD9yPQ4G}27oj9ROWjK+ryIuIFTQ)ZgMVE=I0qlR+txm7 z+8dLS`Wq9~vbXg~tdXl_)3EZ|Fy-2?aM!=>RN=##P5hb-Wna|RHOM}1XEgjhN>B+j z29;-$!_F~G1X-`JEK#=^+cy3%Yn-!c9=DM?=FL4f#Gsh!Wi)IOLKP)QNj0vnfz>8c z;n(wGm!yc;htN;MVSOH}^nxD?=goL_!Z_>}%dtH7_qpx(FT1jc+Zx z!*o!CFjValV<1zIta=+fIjB}^6g73VXickV4%_IA7M1f+l$6K-9iCVvO_R76;)2Di zgKC2@-O4I^Svkq~y?r0}2 zqhvxT5`27rGUYQXB8q8nJsWy87JPKIJ@(&fa!EB6OW(b6e{(cT^Y7&TSvo2gCDGUp zfIf+LOSTKQvO;VIe`Pqf!e;_8&C~4&f1f?7RM7RP1L_ZnbX!L7?{1M}och@Swtt7Q z3@a?U*Wfc!rs2!~P9*#o=Bnh1+Rih^F@`c+vOn4%MbaK8b5!F#7Ab|!P(2G!`c!=;xitH2F)TR#ua-^n}c$f|$!!`Z)L zL~m+J;Sr*zNm-_+_;qjP{xt|l`_F0bRND(%IDs5T zek=;1wdh9k`@A1Xcd&Uwq(K*#XgEr8$VoC}f}kWYOacL&p44Q8__;6&)~i{Y_7$7< zZ9xGkIWVr2=U~sy*3kpp0_4t?^F%#_#8`&D$8QS9pb*2ghq_hs-i8n?pVTnBte(@G zoWbTuSdxre?}U(%&Iy=@@Vb`X?pXqgzJ5`?7!cE!0IC)!nq{-Wa|2uX>+; z3)0OjI57DXG3lp7UVL4!F>0urRrb+32AM=uoj1PpElOl#dsd5#mDTU{F1^IJ3>XFS zAgA%A3tBFdYD&Ikvo%G0d-FSwrlj{y_05Ek)ca9Cv5ydu@205Ai&^ zDxM+!7nc(TZ<3Ft;stv_6Jk>a%BbqJDFS?pwI1`XFs7r7*he9ocKQ~i{J^_dO*w^h z7n3Nx&gvO;)TxkStl$E0L+ome*N>iz3*;Ac#tDOco`rBaGwwg+%AYl2!J=YI@DY%-UZx6$&&17azWGz;|FlXCdb0a{rv~0uQzf z%OOosJA&^$Oq1r0cm5SsEEyez9dnT4_#`b=o+|c0lA>->Ke7K$5l(SX${{ik^H{== zYOhYd;IW!c3F5`~WKv75Ci92NW{F$<`j0mJK2tul0;!HZ1o#d9sL{B#YJ$d7R7cDQTCu6Y9MUd?{aTNDiKJJe`d4d zHv;i{*FkpQGB$e-t)T1z80$pQ_Un5eBZX;yS$5Ofayu@&45z0i%aJY^vIa?<$e%OG zy`z^<_J1qSm_9D4J8Uil-Cy0B?ln>WTtbZ*>+^@bM>PQ{luxqoa^LYThtG>dp9-9# z?+tnNQYX#SNaNEohrd(2Zg9b9eccfnZG?WE?yWp=1+F(QL0RnJ@hB_?QA?4L=`gvb z`s?;iX>TBItxIFs9yn+|IBaslg?Fe7p&oCfdU}WD=b=|6^Q$ueHa;VA^?2%eXX>~2 zlD#~XS;gE-$PecAy`l~ruF?HrUq?l!%5tlxM_;|6y0lO8!3u3-H}%9>S?yy&zGrGo z{sjkMpXZA7=oddfmn9*~^zc;p1*7Lz!waB)6P6$Z-@Ee}l#X4gq{6Zg{yRa=uLeh? z4pobf+^W~KzS_@Y1>|3TWks;G1=N~^-$i2-L%gEg^ST2QhfvObZnZzJ@+L%=-mtqg z#vAba(0xf{@=!c}gum>a1^K$P619Y)oZn^t#Yi#-b`c#Fae(?<+A|$YtH0WZ1TGhG z?$j=pO{^?c>Tbs_13fb>0iBWqNBW4cYd$eHd4)sx$PxXIm6FT?O)jj#f(G|mL`(=j z*472It9+)L+a7iLCS_MScGo@oI(e9=iQnWXJXe~T!W}?N?md-6=~u^Yngp=c9KrUy z4?h-I(NL;KptYHf;+6vI-&KS}s*+1uOILj`V;CTID!|`2n`!6V3G4gnJ#24Eh0g?y zRg~@e0@fjAdZC|KYcwIo$(W$5h`p{w5>hqlUV!xmk{N&*b(dgac%ekjD6CHlg^Ww& z0|AXwphUmx?wVJwM6vjtvO8PsI9+n8bcJgLwZ2IND03`LO#HAXNxd66X%ZA)+)?~87<6n9O0wN8oc^-Po&Tz)k9BK!mHh9gGEUyb36?K4=Mr=gqr&=MQ6Z3+O z$M4Vtollk0*Zje-uGer`lPwvbzBJ}6t1Kx7TyElUYi1DlziXA(y6O5i+jK9K+r4YX$ENMxe`8}?=kj{6;Vy?nQ#yxC4 z&2UC7cf`-}S%{Sq$~=@L-#dIWdWayu3acPxd{h`|;FYgUN(2Zm_H*-+SyG^{5Bi;a z!T-*I&er)=LV5b4b4N1=8Jg?;vHDq&#^s|OxBfq2N$pamH+Mbl9i5t>XUTS&(NR+J72ADl2;3hv{$ayYiBbTYwW{>6<5=N z7-iS*#UEB8T}~c|lDxW$q)m4Y7N{_b-@5jlnmxv^5IGlHxpqnjfGM-H5PAv$?a?F6 z>ukuN4arUMkL5=A`zkx<&o^Ie66=e3;pj^(07?PhNrx=2z3mwl@}*W6Oo>&5Cg-(q zx_SLu&6ooc;|>>IflHgw#~(02v{`7BD(B2ZHx23!IIz4}_d>H6Axc+Iq zSOnOvnxIauk1km@#gt=-kXcbn&faD8NehB%y$yD-3S}5X?AX$9SJRH9u2M=nkx$@cgXc(=mUN z_tO`D(%KgW%8qN`$!`sARu1VH4NL*%&5_LqtYjpg{1&`^OghiLK-8chZc)&eSzEz_ zUw*n8)i8vGcML$UOh1+$mMUmq&JEkba>^Bc300A|&cA72eAB+pYwB%4?L*%CKP|vO z)5hA<>uW-+=f;9Pdu%l}(TW?t#{3&So(}s(ZAxT1AGIb*)31-oMF?<0u>Zbw zkJb|!GBmiCQ1K<&8IP2=AS6D5>;(biViQl*Zmu4UmRQz$X&x~Z5y1VxV?abLGNT97 z)hKwhNi5zZ8*I+cjex@=z7)thDo+NW6hJ#u-bm#~n4Kqta0@+f~ zn~1-Sb5ln1KaH{;a%R+eBdLj~D_9 z%EFU4ySwAShi-fm&-xUH9^MzPWu!)1{F3mXJgd!@`m_jv_Jxue4B0Kxsl8pBnEW;n z^*5C!jDzM0IkgNu@b&x!6BgtClcCw5VP=;RCL(!%1AdW^X3>VG$gd`=GDn71YgtJj z6s$hVU?#_Qg2hFcXK~81|acBLWHv^^g0WTbe{S*o#+@a^#4l>hCHoT&%&l-RCmhv}5kz z#=7z<**VHTf7iSoklfVIizwOrP7pdX_-y{^tigf)7PK%V)i(Eg@3r8t%kA%y{Q1qsnv_`d*dcv0;!C&@+-C zgiJP>Ql)p@%37yctZ;Gtmry4*N){&bOfHiDg=-rCob1pq)(5x(*vNl??)M{_z+Avn zQ5IbG`sE*$4;A+>yP_!bm_OY|U4dqg>5UtFPwIfrV_yAnNaGfY6>7d#xOXJnlszZ^ zZOZJvt%MWpPu)#%v1DfN7^Ar*Cn)QtSOT+aFE&~dPMd}FV&wkeq!NEiQoxM&?N9!fV zXTbJx;&YopZvosW-5A(6e~Lw|LpN?mMrpWrkQ;ZpDmgxj?e%Yfl-z zAO-p!9ESqL-?(&fTajEBzSnknhvzpu*Sh52fd+|5$1bE3p4$<%v# zENf9F9~B4KKtUJ%nD`SbLP6D>h>s9|RZWR9F^<=ZUogH(e6UFQE-%G*4>UNt@(Q3` zOYV!`8KE&LjG9Q%G)#hG@!Lkhd2^h77Qf`$1TyvuNDA>$5=rw`7!mml=`(5b{zFai z!yFlYqL>Zsq{npVy9~$=&>FgD?bFMV_iKvfyfmwWmohzeKK9H&H-+|0FftSZ(R*Pz z$+CQF`BHjKM*m@N@>^%429+5QpiP+uO%E23zGJcWaWTlsaW;CTx_1i6;3p973f8C> zRw^T5?;EsAlYjnPOJRrjYzh4ULO`6#rmctVg`tiKD0)r9HxErf$h6m*QsEesl)0~o zql{m*j8E9clBCH8I2~$ny}0G*I;s2)BWcBVD@5P;9^_H4lC6pJw<91~pq8pH??}44 zi`SlV4Y^xW5VKlw_UdLA%n5S#T4X#TsF#5+KZ@)d!fs?Ic*|ew5&j3tPuKG(8`P}| zmy=T^STi>=TbI3r6(e*ANeOq`IUm5k64!8dJs=0kV+aAhjhki&4>hOw3^y|uwy;g< zq!~nE``_Pe)0Mi~tNT)e09VFJ8o0yY`e3|^PwFpzQuM~|m@koq2bx#pMWw+?Aoxi4 zxNN7Gn5QKV=k>EVN9piz@^{c8LhAZrV)bAX|{_l8{{qx=3)Mx7Cm6`O7H>hbpAN~NJpzG=L zORZO;A?KH$Isy&GWtB=)zRHARy!+{zM0QM#cW?ist7+yJ9mBPa$xu21Ffa4Rdvk$*FOJWDAErw;1YZFqh!|pJJlkw#D#9BONg!qm zu_qq=qm(mjAUFw2Tf4PF!eu_S*Qar^ip54s^Q%u|g6HybKTkeA zk5uaPqex(_b9_twEImuE5a>F0| zb0d?xaMM<9CdxGVTY;a93Vz<_z7|| zK;QN+-9cJ5DOOF{4K9cd=^d?r1G1EpeEtasHgyO5GD^Y5Ndvnf*E_3!D$_aniP*?Te+SO6 zqX)Q8xX^jfNh__b{}wQb1IXzTR~eNmaVmRDRe92QMB^OvAgUrS@z2DRn${fgp$!** zxH5dB<>5=k1|dE>2WHNA_8!*CtX1u*M3eB5j3j{4c8&=bR=VBsTcl>Q;`Hocb0q>! zCw%2cZg6#K#h*20$Vg-wk|ttl!k0ps%K`vyzR}U~K)sC~mErfT*ZMQnA(SImX>>f1 z^UvWE?VWbY1PMVqj6CPR7w+abu-+C5%7g4+XY!=jemzJYZ<0xN|9Js(D8Nd&l0@zR zy?L=Q?lol@`}8AcFpDwVX;9dG6*y{65=_U!z#N24EL%ykf97P)k!V*r-UCyY?S~xF~be$ybd9jC>Y#U9710D z_w@WfCfdh^+wb&#)jXYsHgbI3X$(lskK@52dp)j7|U17QWVTe{elDTV>t+cUa}j0GaGPFYpC$ zfj5cQJt%<6598f}Dpfuv3I*N=ZY5fa>)bc{tl!$~tJHyfVZsu&X4JJw!b-%~?@4G} z3=Zj8eZ*^+S94TRYIJsSc9W0W>Rm7ucD<-@6X>Sm)V!E8iz+E=~PBqrKy~Yl> z#7;D(L>dJWiB{rWPJk2X%lXMp#Y3g zaWo9Lo-Aq!D*{opPQ1e+W#6fvtkU0!0(kl7mtb4z@0@5_rz9q2lq;NJz%J>d!8MZe zT~6snjp;20#LbrHm{hNrv8Ay%m4Qx>kIS&03 z+!nUF3GHd6N0SY9-_N%|70wVt!7L;*G|oreQa3@$!Er+(N+jpd8jltvpoOK(VD5fu za9tph>Z+CXOd0lNMsymiQuHckE0*b~Ah$!tM{W~`frjF{UlSqm@U@;mXNQWQX6kQf z7b6co^cBC1z6^bc zw8SI48NU$uijJ-GZ;e&Vx9lVkJ-oDNc(t8#R<;hY$Apg7QrDG;9xi~}Rca6g(=c+z zvLPrBSaG9!QJ8-aL2zclgD4pk)exF-1DTn`nEwdcWo89tM45kK)YR0&dnDm%rt?yn zecpZ>w{N7!@*!52Z?m9;1z}t&NZiqU5gd>7%8v^SjgW|?WFvX6I^i82jm&6mQY1)N zxcen+O1;(JbNXDLz&?g(FYF{3i`=>jtBP4p8vdF}w_d@T5B6+CaG)6z@$UO6 zKbSHrT`k@ucO5rU`Z$%UwJIBCJW!;P|FjuA&o#tT_}Kaf4js!OoWhzvwc9cS3E)=A ziy{MMV_{cOlH|%4WgDuo-_`uU{{(M90GZ?0bv~F?jb(lVoHiyatNty8b^rR$2~V9s z@t-bluKNLo*;I(4V@~zIy+=PKbL`At{9YOFVw;$-=wk=Zgl2idzE-lZ@4s`XWl=CfJ@reoZrG&mU8 zQ!ZXOoe~HuJZ{yaui`fRZ8l}Lxs?Sv4@5cCu??$I{um{t7;#}Vp5?j9t#>&Ak~>xu z|3g=Fk)c9jc0sNKR!SvHqEmJmpJ$#PJu|qr+TIgCgF1IgUNyHPtp6TY)n=$&gCJ~2 zPYfjTj3mmkD}_dcXlxAQ1c=Gc^4~sOw~X54`!L$a0qx7AyC*^n5gaZCk9HlcM`*;P z3oB9Gzs|RsXsCp~lzLnSc#uXWK$NHxQs6D+6{yjf!m&{mGEi)O3?BDxr_Br>z=wU?-ONz1(oQ^k5aH0X~I zYM+_z55FO$IQqgg@6P4T2{<6X4!;uT#8-cOw_Aw1jD)*!anp zFl>oxPBZ|qFnu&*RY`?&a5A{L_8X8(W`~A9DX~;P%1GqU^Z|44enEYNnMHZUE#}9W zgkEVbvRGFGQN3y_1k`7Bzqs~!5uIhCZ?^TP<#=@Q-hm_~uD|E2FKBj&9iU%px{?6~ z6cXtfXEr&Jq{z1Mzfa3J4-$={?ACCbZX>kv5Wz&@Rzo*oplebQk(|EBw-0`hB&)c{ zp~;Xs;Fgpbsp5d~QD~%_P^!f76E$*URi!1wmz8Q|Dz7Oi|EhdjhO!yZ^D|?0s)#w7 z@OP4Yd`t0!1^2?nu!z(NXXeSgG3;IdR1Czw5qG4eH{ILJf_{;5({nK-o<0wrU& z@&`ow7zDQ`2ouHIQw)S~{7_*vz?VGC`SyFlTk5%4JaM|hZS@*W;@iR)?g0kWTYzWs zAUWd;4?nc9`bJQKxbs!r{g6oDnJ7p$>`u}*{&Zkc&irfwE8C@YM8%h%E!pMU8HDuQ z63shI`o4>szledxVuHVsWuEv;0q<@NXW!rhEOt4ACuor*NvoKY$q#BXplbL1yc#ZUUrty}%ME0jNdAwJCz04x zYo(FryFt68tx=`_hg%#_o(awqU9-2xrjDOoFei+OOBZL^=`Ntli;f@ws7(4e z{%?*B#`NZIa~@OU#NFz3ewxcN3EtB4yC4C4;%{jdO88ETO$9X+QrZiIjMlkQZN_u^ z88^2=#)7W_R+oiM_KXXOdU_aW?TsIFmn$9OdJ~oVZ=hawa6jr*hw;JR`or$@=RqjX zdVBk%5krI%=FN6RVp6sv=GPw;%+)ATrOcH$k_~ z@;_QAYu&o^fU@eT$>8;2zzNj(wzaOkGq8WP4Fd7SCcstzNU?+K|Bj(uBOGs*7?A-iB#DRlAytZ zFgG_*Hht|Z^ZCi5jIZ4R(s3v}R+GBrqqV31RsyX>z3f!l0bCO*7 zzvZ5}l>#Du;&a-TO~=@4Pn^8-Qr?_XF z>5LZK07UOHG+1=CzXQGmC*@#7f4rVguldu`sKpT#L0%jLcZ<$Bs#z`*O=1_c!=Ymx z*rt@BCn7*W${R`rmk2A&e1>LZ2Y5T2u(egFlw@dEe}KQyz8?3l8|OS2N<&UzyprF3hXH4hJ;*eDD?8cVy#Fuqw=K z!4~q`a1{c7b}+O12SI7}A&vmYRT>6vsE-mKQN{~1Epz^dgA4-=p9OxoIn>IsveMGk z<8``5=NEE75{!8b_QY-azo4?V!u3CXvVt zjQE~BKkh5|PFQl3Fbo;e7Hv=%p8Y)zsliKorpB_c5hEp>kd0np z;qF)(m%S|X z&$6b_%`mw-0|ak~=hm4ql@Ie8Y1h<*NxQb)0xQPR(ic6GT0N~=cZ^kWvsIH`i}7az z$H^zPJKILk^Uoii+(fW11r|EztAEw6G^pS<`1IJIoYQd0w6>XgDKvWu)bLZJk!xjD zjH#<(S){SWt`7Ot7_U0?+^Nq18A;q1^>yRLd+#sCUuy`z?T!N2%TXuNf7Kxf^{++A z?{7&Es=@%!&{X=qO)_b+`J^lY=ud9AA+rwZQ)&d+&?^8Txh!Y$TIWe zMLZd$I>bQ*Dj4KZ<-IWe`>PJ7D>s| zUz}xX(pq(KvG(n&3)B;5m7Doe3U%|y1m#hwUpEa0|K(Q)C&H&l2KipyGh}WAKYtLl z_zZNd@>rc+T#5agWfD=U!hk=^=NXVejh%P<&hz;vq#I*+5gvl3KM1!a;Ibl$V}2Zc z@$;kG^CR2uE}#C8t?YTv06?!wa9+-wm=~e3W#k{hV9ggRj#5Uk>RpDvu5XO#B#g!~ zl0q$rpeqlJTM=Wmj>(nYS_K<1UAimEuD>hu<%FxTL$D?-KSaMxfv;g+iK<@l zJ~++)7PfH}F(|CNVx}^~NChZg;acqM@c@fdO+diEvYM}VriB(>KNG2PBreN!s(zsfV5bal+j zdpmS2Dw4=#U}}1`+?&mWYR6&?_}vRsy@BB4CDi@5u+}#(`B`P`!UB{Ij%vOCq?4id zcfPP$l9h!tipEr_Red`j9B}=o`oV zHjphW&c`wnuEjq4!ubQz6$f$C`KM!os28HMR7dh51Gf8z1Mki7dTfw67YUgKRUQu! znJ__F;J4_2Cq$G4l&m|GjuHxTlm5U6g8h7ILtS{kJ|nn%;2h&mF$%lVloO>0niD6v zBzA=~i?L}WOQ5L`<0LIR5$8T#LuD#Qg_oWL?y{1uj0e##(}4l}KJyoSk6~Bs0|S0D z7K+6s-!MB`eOh^zJC7Y8b{kYq;%e4i-*z`UzWl5KprSiJ)DMyz-tM(LTY0N~0r(XY zes=t96;?{ze%1g&b4aW>uF~shD5EZT`R@+TH-Jp`-o0CJwx86!?CNLJ3K98pFXRv! z+DAPb)p4uayNmkQ{PO)fED@m53QZ6IhystQCx<--UmfrKn{m0g*_`bycbV$AJ@`iK z)o=Dm8bhGd{!{NP@c=o`(zGxk27Z0cy~1KH-9lWAVvF6Z;YjixYOtdcKBAWZP>DAv zqf=<`18)Sl9vW!T>r_}o&J{#Xg+D3U5+qwS*?6}d{9x7bb+YHnim(4!;@rJrmIgL2& zF*BzL*E7SVHtqLR=ZdQUgAGti-k%N?Azw^7#^2<43ESOhBVxQYA_a4dHLK$)88 z2`3;4cv_`n=y`xP5$rM(48enA(yFMZ1Mkii&oktz8kS8~#Yd%bcsUJ_P zGmlHz2`)Mbtau7g04fRwrA>rVY}1w{!J2N!Ct=^-{u--KtM>%Oh@y6$A$h6vpgZel zA%ml@y|YRW(s-)3%5A2m*{7!cf9ZTUHH>$w=mSrO*JV7_c3@5C@{N|(`&IssTzPwq z;fIFq?q@)VukER*6IA8}D+z1D&kTcwTRr80>D#uB+pv%OvZr#D;DawbaIwdPGr!MR zZO0BVUKmuPRN+0u{(Ephgw_=%wUf&N^Tj1s%y;39Nyi0@dgwSW{-=yF(Evb0N)qw| zvhcCL+-n?SeW58n@qaX(bx_p(`}N5smj-DTr5mI>1?iM7=>|z@*rgGW4(V=`luqdu zq`SL2p3nV#=JziQ!!XRe_qxt?&g+;WI!w@$Cui)%0wzb1Iq}qA-{SYzvFmkWN?;Nm zt;F1s2#}!8XyQ3jvgn9+oHj`69~4j!Q@P|1Q7D$YT96V`>#zjxIn%jFB^`j!VysL^ zKIb9ip40TWSkN>D-0;Dt#$Po^e5;ePI|#m=N+0FD-xczfgo#)pJRz3mmW0mR<0yrF zM>`amuEhHHZ{5rmkx&CgaV?}|hzk@4JW)6WLep*EA^)EjU>~;iOm3G)|K9=pDya`; zcmpux9m8(7K$3dzOAK<7R;G}Xpc6va3y?bbGm2DpbQQsZo7`V}H9WK0L=hNhSNNZ+ zFdq=rXkh$l9M6O*C&y_GXh+mXad}b}U26IqtRM1lm=Ig?bI>o&eODpVhWQ~(-`%Sao8Sb@JUV+!%LxW%Yrp23EDJS6^F(r`Sxi4%Cb9$SxRDm!wZSh za)c0w9HCMYy-ol94qlH!k(EHJ@`{*wXhkIxR@Xqup2IAE$N&rQ{{M!%+vmsGK8)eQKAecT)YW{o40F<#;=}{nhPE3s63E z+nt;@b+w;1?2j_CU(Uw(YP}z+%m&;qmyaOb7Dxlug4S{H!7i5H z&ey&eo&`Z2qY`&jwR==>{B092P*t!8OA^wi2>t3tKSnhN)EY3So|5_2wo7z JU_ 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

q@9SV#Kfd~ARn$0KE-(Hb;5EJoDfie`54Uz@& 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ýrR-G-=vtZu(>{Ark>-3#}Nq7L7Hn6Bi!9`J*8Z zO&~EvEn@X)(ZalV@R@t~_gKSiWa`i@yF$Oqm7W&Ys#M_Xp8NBXJW@< zIL3zyHF?OQ4ZU$Sie$YD=?M1)=AJE;+;IOtg#gkw8VrN+Zd z`&ENt0$`kL^&m}|HT#OF3!rL>U%r>>%oSMX)Ii#vgtro*iD4KHef;zEx81gx0>IZ= zl7C+WCF~%+?8pUn1`+z%ElH0E;b?){qyxKoZg4i=viuet$~~DSMSVj zxGR!+2rNK4B#C5%r!*K7RZxPbqQzyKyx@nnJ^hGEnz=c@NUDF>M&1X`>;BTD*gLgB zdu&=V|hHUciJf3%!2UJx{O7?m&jF zVXw7{EWZju%=%>)f(UPGqnG9u{@CAm-AVb1dhFahT&=u3-}gk8Z>`z`}CpZXeUhsF~>!dlHB6 z1i${vk>oa3aI$CS2KS8hlcXFqc1pgkXB zJJ9gFbO~+;+-+aoA98H{JcjtHO{2|!bu~?V%sSD+)fPM1{Y$jTa9n>*g~W_ONIMygcHxy=)roYZ;Ao1ec93Gzt^r9+V7}SpIGA5_jU+q6LzOpPkMT zjqh^G(8rGIyDCn*@bs8<&kE4_2G%R)6vuu?7{A z>S^sre@$Ao0IqRXcM}4G^<6DKwO8_Yr5s~4ocR!GIRi|bJ5?>@9-fjLv&*g9N zNi02bGTAfKanr1fg1T8Ge{m@*!-`MBS!O46>M&V@?mu^`f^Slp9=k)Ap_o`3&Z;4W za*IUdtk^L>LPLk?(UuVy5eO1EJQ86L``pgi35GA&n{y>{L3!dj1hQ6IREcz~jNE({cpR&1?%6!Fk2fRf#(xoN<7PuC2d&1l+Qw4jl4WZe=(N zZyVm5HXDW?WCqv)u-%`~DnMCvc=EOyvR7yI1_+$+sFY;XWq#PvA;F6^JAQqDO@7X_ z%H`Y%h~$;K@O+sUoV0lW1(?<~M6 cKp>i&k6!_2Xeb#wTeeXc#+<;ROvpV2y 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 literal 0 HcmV?d00001 From 8a1dec25fbf6f003f82b804422d05dbc76667e52 Mon Sep 17 00:00:00 2001 From: "Huabing (Robin) Zhao" Date: Tue, 13 May 2025 12:11:22 +0800 Subject: [PATCH 60/66] temporarily disable the backend tls test (#6030) Signed-off-by: Huabing (Robin) Zhao Signed-off-by: Arko Dasgupta --- test/e2e/e2e_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index c7de5de62e..133b16835c 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -41,6 +41,7 @@ func TestE2E(t *testing.T) { skipTests := []string{ tests.GatewayInfraResourceTest.ShortName, // https://github.com/envoyproxy/gateway/issues/3191 + tests.BackendTLSSettingsTest.ShortName, // https://github.com/envoyproxy/gateway/pull/6029 } // Skip test only work on DualStack cluster From 65e6b5cf91e3bdacfdce652fa3f19f56f03bb0f1 Mon Sep 17 00:00:00 2001 From: "Huabing (Robin) Zhao" Date: Tue, 13 May 2025 12:29:04 +0800 Subject: [PATCH 61/66] Fix lint (#6031) * temporarily disable the backend tls test Signed-off-by: Huabing (Robin) Zhao * fix lint Signed-off-by: Huabing (Robin) Zhao --------- Signed-off-by: Huabing (Robin) Zhao Signed-off-by: Arko Dasgupta --- test/e2e/e2e_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 133b16835c..399087cb71 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -41,7 +41,7 @@ func TestE2E(t *testing.T) { skipTests := []string{ tests.GatewayInfraResourceTest.ShortName, // https://github.com/envoyproxy/gateway/issues/3191 - tests.BackendTLSSettingsTest.ShortName, // https://github.com/envoyproxy/gateway/pull/6029 + tests.BackendTLSSettingsTest.ShortName, // https://github.com/envoyproxy/gateway/pull/6029 } // Skip test only work on DualStack cluster From 9aeb15313e84ea2601389ce6802b77c80e86c823 Mon Sep 17 00:00:00 2001 From: Takeshi Yoneda Date: Mon, 12 May 2025 23:01:47 -0700 Subject: [PATCH 62/66] fix: allows offline k8s controller to use non default CRDs (#6020) * fix: allows offline k8s controller to use non default CRDs Signed-off-by: Takeshi Yoneda * workaround Signed-off-by: Takeshi Yoneda --------- Signed-off-by: Takeshi Yoneda Co-authored-by: Arko Dasgupta Co-authored-by: Huabing (Robin) Zhao Signed-off-by: Arko Dasgupta --- .../provider/kubernetes/controller_offline.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/internal/provider/kubernetes/controller_offline.go b/internal/provider/kubernetes/controller_offline.go index e6d96bbf9f..74f64d1a01 100644 --- a/internal/provider/kubernetes/controller_offline.go +++ b/internal/provider/kubernetes/controller_offline.go @@ -75,6 +75,23 @@ func NewOfflineGatewayAPIController( envoyGateway: cfg.EnvoyGateway, mergeGateways: sets.New[string](), extServerPolicies: extServerPoliciesGVKs, + // We assume all CRDs are available in offline mode. + bTLSPolicyCRDExists: true, + btpCRDExists: true, + ctpCRDExists: true, + eepCRDExists: true, + epCRDExists: true, + eppCRDExists: true, + hrfCRDExists: true, + grpcRouteCRDExists: true, + serviceImportCRDExists: true, + spCRDExists: true, + tcpRouteCRDExists: true, + tlsRouteCRDExists: true, + udpRouteCRDExists: true, + // TODO: enable this for consistency after the foundamental fix is available https://github.com/envoyproxy/gateway/pull/6021. + // In practice, this won't affect any user-facing reconciliation logic for now but it might in the future. + backendCRDExists: false, } r.log.Info("created offline gatewayapi controller") From d673c072e86d8fc15450485149d714ae9f054fc3 Mon Sep 17 00:00:00 2001 From: zirain Date: Tue, 13 May 2025 18:34:15 +0800 Subject: [PATCH 63/66] e2e: refactor ratelmit test (#5997) Signed-off-by: zirain Signed-off-by: Arko Dasgupta --- test/e2e/testdata/local-ratelimit.yaml | 128 ++++++++----- .../ratelimit-header-invert-match-local.yaml | 41 ---- test/e2e/tests/local_ratelimit.go | 177 +++++++----------- 3 files changed, 153 insertions(+), 193 deletions(-) delete mode 100644 test/e2e/testdata/ratelimit-header-invert-match-local.yaml diff --git a/test/e2e/testdata/local-ratelimit.yaml b/test/e2e/testdata/local-ratelimit.yaml index cf3ae5da06..dd819d6ca8 100644 --- a/test/e2e/testdata/local-ratelimit.yaml +++ b/test/e2e/testdata/local-ratelimit.yaml @@ -5,23 +5,23 @@ metadata: namespace: gateway-conformance-infra spec: targetRefs: - - group: gateway.networking.k8s.io - kind: HTTPRoute - name: http-ratelimit-specific-user + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: http-ratelimit-specific-user rateLimit: type: Local local: rules: - - limit: - requests: 10 - unit: Hour - - clientSelectors: - - headers: - - name: x-user-id - value: john - limit: - requests: 3 - unit: Hour + - limit: + requests: 10 + unit: Hour + - clientSelectors: + - headers: + - name: x-user-id + value: john + limit: + requests: 3 + unit: Hour --- apiVersion: gateway.envoyproxy.io/v1alpha1 kind: BackendTrafficPolicy @@ -30,16 +30,16 @@ metadata: namespace: gateway-conformance-infra spec: targetRefs: - - group: gateway.networking.k8s.io - kind: HTTPRoute - name: http-ratelimit-all-traffic + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: http-ratelimit-all-traffic rateLimit: type: Local local: rules: - - limit: - requests: 3 - unit: Hour + - limit: + requests: 3 + unit: Hour --- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute @@ -48,15 +48,15 @@ metadata: namespace: gateway-conformance-infra spec: parentRefs: - - name: same-namespace + - name: same-namespace rules: - - backendRefs: - - name: infra-backend-v1 - port: 8080 - matches: - - path: - type: Exact - value: /ratelimit-specific-user + - backendRefs: + - name: infra-backend-v1 + port: 8080 + matches: + - path: + type: Exact + value: /ratelimit-specific-user --- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute @@ -65,15 +65,15 @@ metadata: namespace: gateway-conformance-infra spec: parentRefs: - - name: same-namespace + - name: same-namespace rules: - - backendRefs: - - name: infra-backend-v1 - port: 8080 - matches: - - path: - type: Exact - value: /ratelimit-all-traffic + - backendRefs: + - name: infra-backend-v1 + port: 8080 + matches: + - path: + type: Exact + value: /ratelimit-all-traffic --- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute @@ -82,12 +82,54 @@ metadata: namespace: gateway-conformance-infra spec: parentRefs: - - name: same-namespace + - name: same-namespace rules: - - backendRefs: - - name: infra-backend-v1 - port: 8080 - matches: - - path: - type: Exact - value: /no-ratelimit + - backendRefs: + - name: infra-backend-v1 + port: 8080 + matches: + - path: + type: Exact + value: /no-ratelimit +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: ratelimit-invert-match + namespace: gateway-conformance-infra +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: http-ratelimit-invert-match + rateLimit: + type: Local + local: + rules: + - clientSelectors: + - headers: + - name: x-user-id + value: one + - name: x-org-id + value: test + invert: true + limit: + requests: 3 + unit: Hour +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: http-ratelimit-invert-match + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + rules: + - backendRefs: + - name: infra-backend-v1 + port: 8080 + matches: + - path: + type: Exact + value: /ratelimit-invert-match diff --git a/test/e2e/testdata/ratelimit-header-invert-match-local.yaml b/test/e2e/testdata/ratelimit-header-invert-match-local.yaml deleted file mode 100644 index 0b390af221..0000000000 --- a/test/e2e/testdata/ratelimit-header-invert-match-local.yaml +++ /dev/null @@ -1,41 +0,0 @@ -apiVersion: gateway.envoyproxy.io/v1alpha1 -kind: BackendTrafficPolicy -metadata: - name: ratelimit-specific-user - namespace: gateway-conformance-infra -spec: - targetRefs: - - group: gateway.networking.k8s.io - kind: HTTPRoute - name: http-ratelimit-specific-user - rateLimit: - type: Local - local: - rules: - - clientSelectors: - - headers: - - name: x-user-id - value: one - - name: x-org-id - value: test - invert: true - limit: - requests: 3 - unit: Hour ---- -apiVersion: gateway.networking.k8s.io/v1 -kind: HTTPRoute -metadata: - name: http-ratelimit-specific-user - namespace: gateway-conformance-infra -spec: - parentRefs: - - name: same-namespace - rules: - - backendRefs: - - name: infra-backend-v1 - port: 8080 - matches: - - path: - type: Exact - value: /ratelimit-specific-user diff --git a/test/e2e/tests/local_ratelimit.go b/test/e2e/tests/local_ratelimit.go index f35782de8c..1c6d79a8ae 100644 --- a/test/e2e/tests/local_ratelimit.go +++ b/test/e2e/tests/local_ratelimit.go @@ -22,18 +22,41 @@ import ( ) func init() { - ConformanceTests = append(ConformanceTests, LocalRateLimitSpecificUserTest) - ConformanceTests = append(ConformanceTests, LocalRateLimitAllTrafficTest) - ConformanceTests = append(ConformanceTests, LocalRateLimitNoLimitRouteTest) - ConformanceTests = append(ConformanceTests, LocalRateLimitHeaderInvertMatchTest) + ConformanceTests = append(ConformanceTests, LocalRateLimitTest) } -var LocalRateLimitSpecificUserTest = suite.ConformanceTest{ - ShortName: "LocalRateLimitSpecificUser", - Description: "Limit a specific user", +var LocalRateLimitTest = suite.ConformanceTest{ + ShortName: "LocalRateLimit", + Description: "Make sure local rate limit works", Manifests: []string{"testdata/local-ratelimit.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { - t.Run("limit a specific user", func(t *testing.T) { + // let make sure the gateway and http route are accepted + // and there's no rate limit on this route + ns := "gateway-conformance-infra" + routeNN := types.NamespacedName{Name: "http-no-ratelimit", Namespace: ns} + gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} + gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) + + expectOkResp := http.ExpectedResponse{ + Request: http.Request{ + Path: "/no-ratelimit", + }, + Response: http.Response{ + StatusCode: 200, + }, + Namespace: ns, + } + expectOkReq := http.MakeRequest(t, &expectOkResp, gwAddr, "HTTP", "http") + + // keep sending requests till get 200 first, that will cost one 200 + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectOkResp) + + // the requests should not be limited because there is no rate limit on this route + if err := GotExactExpectedResponse(t, 10, suite.RoundTripper, expectOkReq, expectOkResp); err != nil { + t.Errorf("fail to get expected response at last fourth request: %v", err) + } + + t.Run("SpecificUser", func(t *testing.T) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "http-ratelimit-specific-user", Namespace: ns} gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} @@ -59,9 +82,19 @@ var LocalRateLimitSpecificUserTest = suite.ConformanceTest{ }, Namespace: ns, } - expectOkReq := http.MakeRequest(t, &expectOkResp, gwAddr, "HTTP", "http") + // should just send exactly 4 requests, and expect 429 + + // keep sending requests till get 200 first, that will cost one 200 + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectOkResp) + + // fire the rest request + if err := GotExactExpectedResponse(t, 2, suite.RoundTripper, expectOkReq, expectOkResp); err != nil { + t.Errorf("fail to get expected response at first three request: %v", err) + } + + // this request should be limited because the user is john expectLimitResp := http.ExpectedResponse{ Request: http.Request{ Path: "/ratelimit-specific-user", @@ -74,24 +107,9 @@ var LocalRateLimitSpecificUserTest = suite.ConformanceTest{ }, Namespace: ns, } - expectLimitReq := http.MakeRequest(t, &expectLimitResp, gwAddr, "HTTP", "http") - - // should just send exactly 4 requests, and expect 429 - - // keep sending requests till get 200 first, that will cost one 200 - http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectOkResp) - - // fire the rest request - if err := GotExactExpectedResponse(t, 2, suite.RoundTripper, expectOkReq, expectOkResp); err != nil { - t.Errorf("fail to get expected response at first three request: %v", err) - } + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectLimitResp) - // this request should be limited because the user is john and the limit is 3 - if err := GotExactExpectedResponse(t, 1, suite.RoundTripper, expectLimitReq, expectLimitResp); err != nil { - t.Errorf("fail to get expected response at last fourth request: %v", err) - } - - // test another user + // this request should not be limited because the user is not john expectOkResp = http.ExpectedResponse{ Request: http.Request{ Path: "/ratelimit-specific-user", @@ -110,15 +128,8 @@ var LocalRateLimitSpecificUserTest = suite.ConformanceTest{ t.Errorf("fail to get expected response at first three request: %v", err) } }) - }, -} -var LocalRateLimitAllTrafficTest = suite.ConformanceTest{ - ShortName: "LocalRateLimitAllTraffic", - Description: "Limit all traffic on a route", - Manifests: []string{"testdata/local-ratelimit.yaml"}, - Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { - t.Run("limit all traffic on a route", func(t *testing.T) { + t.Run("AllTraffic", func(t *testing.T) { ns := "gateway-conformance-infra" routeNN := types.NamespacedName{Name: "http-ratelimit-all-traffic", Namespace: ns} gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} @@ -141,20 +152,8 @@ var LocalRateLimitAllTrafficTest = suite.ConformanceTest{ }, Namespace: ns, } - expectOkReq := http.MakeRequest(t, &expectOkResp, gwAddr, "HTTP", "http") - expectLimitResp := http.ExpectedResponse{ - Request: http.Request{ - Path: "/ratelimit-all-traffic", - }, - Response: http.Response{ - StatusCode: 429, - }, - Namespace: ns, - } - expectLimitReq := http.MakeRequest(t, &expectLimitResp, gwAddr, "HTTP", "http") - // should just send exactly 4 requests, and expect 429 // keep sending requests till get 200 first, that will cost one 200 @@ -165,58 +164,22 @@ var LocalRateLimitAllTrafficTest = suite.ConformanceTest{ t.Errorf("fail to get expected response at first three request: %v", err) } - // this request should be limited because the limit is 3 - if err := GotExactExpectedResponse(t, 1, suite.RoundTripper, expectLimitReq, expectLimitResp); err != nil { - t.Errorf("fail to get expected response at last fourth request: %v", err) - } - }) - }, -} - -var LocalRateLimitNoLimitRouteTest = suite.ConformanceTest{ - ShortName: "LocalRateLimitNoLimitRoute", - Description: "No rate limit on this route", - Manifests: []string{"testdata/local-ratelimit.yaml"}, - Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { - t.Run("no rate limit on this route", func(t *testing.T) { - ns := "gateway-conformance-infra" - routeNN := types.NamespacedName{Name: "http-no-ratelimit", Namespace: ns} - gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} - gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) - - expectOkResp := http.ExpectedResponse{ + // this request should be limited at the end + expectLimitResp := http.ExpectedResponse{ Request: http.Request{ - Path: "/no-ratelimit", + Path: "/ratelimit-all-traffic", }, Response: http.Response{ - StatusCode: 200, + StatusCode: 429, }, Namespace: ns, } - - expectOkReq := http.MakeRequest(t, &expectOkResp, gwAddr, "HTTP", "http") - - // should just send exactly 4 requests, and expect 429 - - // keep sending requests till get 200 first, that will cost one 200 - http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectOkResp) - - // the requests should not be limited because there is no rate limit on this route - if err := GotExactExpectedResponse(t, 3, suite.RoundTripper, expectOkReq, expectOkResp); err != nil { - t.Errorf("fail to get expected response at last fourth request: %v", err) - } + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectLimitResp) }) - }, -} -var LocalRateLimitHeaderInvertMatchTest = suite.ConformanceTest{ - ShortName: "LocalRateLimitHeaderInvertMatch", - Description: "Limit a specific user unless in a specific org", - Manifests: []string{"testdata/ratelimit-header-invert-match-local.yaml"}, - Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { - t.Run("limit a specific user", func(t *testing.T) { + t.Run("HeaderInvertMatch", func(t *testing.T) { ns := "gateway-conformance-infra" - routeNN := types.NamespacedName{Name: "http-ratelimit-specific-user", Namespace: ns} + routeNN := types.NamespacedName{Name: "http-ratelimit-invert-match", Namespace: ns} gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) @@ -226,11 +189,11 @@ var LocalRateLimitHeaderInvertMatchTest = suite.ConformanceTest{ Namespace: gatewayapi.NamespacePtr(gwNN.Namespace), Name: gwapiv1.ObjectName(gwNN.Name), } - BackendTrafficPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "ratelimit-specific-user", Namespace: ns}, suite.ControllerName, ancestorRef) + BackendTrafficPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "ratelimit-invert-match", Namespace: ns}, suite.ControllerName, ancestorRef) expectOkResp := http.ExpectedResponse{ Request: http.Request{ - Path: "/ratelimit-specific-user", + Path: "/ratelimit-invert-match", Headers: map[string]string{ "x-user-id": "one", "x-org-id": "org1", @@ -244,21 +207,6 @@ var LocalRateLimitHeaderInvertMatchTest = suite.ConformanceTest{ expectOkReq := http.MakeRequest(t, &expectOkResp, gwAddr, "HTTP", "http") - expectLimitResp := http.ExpectedResponse{ - Request: http.Request{ - Path: "/ratelimit-specific-user", - Headers: map[string]string{ - "x-user-id": "one", - "x-org-id": "org1", - }, - }, - Response: http.Response{ - StatusCode: 429, - }, - Namespace: ns, - } - expectLimitReq := http.MakeRequest(t, &expectLimitResp, gwAddr, "HTTP", "http") - // should just send exactly 4 requests, and expect 429 // keep sending requests till get 200 first, that will cost one 200 @@ -270,14 +218,25 @@ var LocalRateLimitHeaderInvertMatchTest = suite.ConformanceTest{ } // this request should be limited because the user is one and org is not test and the limit is 3 - if err := GotExactExpectedResponse(t, 1, suite.RoundTripper, expectLimitReq, expectLimitResp); err != nil { - t.Errorf("fail to get expected response at last fourth request: %v", err) + expectLimitResp := http.ExpectedResponse{ + Request: http.Request{ + Path: "/ratelimit-invert-match", + Headers: map[string]string{ + "x-user-id": "one", + "x-org-id": "org1", + }, + }, + Response: http.Response{ + StatusCode: 429, + }, + Namespace: ns, } + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectLimitResp) // with test org expectOkResp = http.ExpectedResponse{ Request: http.Request{ - Path: "/ratelimit-specific-user", + Path: "/ratelimit-invert-match", Headers: map[string]string{ "x-user-id": "one", "x-org-id": "test", From fdedc75a779d269f334be29438ca446cf9c29dfe Mon Sep 17 00:00:00 2001 From: Ryan Hristovski <61257223+ryanhristovski@users.noreply.github.com> Date: Tue, 13 May 2025 12:27:03 -0400 Subject: [PATCH 64/66] moved shared under rules (#5944) * moved shared under rules Signed-off-by: Ryan Hristovski * Fix some logic Signed-off-by: Ryan Hristovski * fix rule logic Signed-off-by: Ryan Hristovski * fix some tests Signed-off-by: Ryan Hristovski * fix tests Signed-off-by: Ryan Hristovski * Fix descriptor hierarchy Signed-off-by: Ryan Hristovski * comments Signed-off-by: Ryan Hristovski * fmt Signed-off-by: Ryan Hristovski Co-authored-by: Arko Dasgupta Co-authored-by: Huabing (Robin) Zhao Signed-off-by: Arko Dasgupta --- api/v1alpha1/ratelimit_types.go | 29 +- api/v1alpha1/zz_generated.deepcopy.go | 10 +- ....envoyproxy.io_backendtrafficpolicies.yaml | 25 +- ....envoyproxy.io_backendtrafficpolicies.yaml | 25 +- internal/gatewayapi/backendtrafficpolicy.go | 34 +- internal/gatewayapi/helpers.go | 9 +- ...y-buffer-limit-out-of-range-error.out.yaml | 1 - ...y-buffer-limit-with-invalid-value.out.yaml | 1 - ...backendtrafficpolicy-buffer-limit.out.yaml | 2 - .../backendtrafficpolicy-compression.out.yaml | 1 - ...ndtrafficpolicy-dns-lookup-family.out.yaml | 2 - ...ndtrafficpolicy-http-upgrade-spdy.out.yaml | 1 - ...fficpolicy-http-upgrade-websocket.out.yaml | 1 - ...endtrafficpolicy-override-replace.out.yaml | 5 +- ...ckendtrafficpolicy-request-buffer.out.yaml | 2 - ...ndtrafficpolicy-status-conditions.out.yaml | 6 +- ...fficpolicy-status-fault-injection.out.yaml | 3 - ...y-strategic-merge-global-ratelimit.in.yaml | 115 +++++ ...-strategic-merge-global-ratelimit.out.yaml | 443 +++++++++++++++++ ...cy-strategic-merge-local-ratelimit.in.yaml | 113 +++++ ...y-strategic-merge-local-ratelimit.out.yaml | 444 ++++++++++++++++++ ...trategic-merge-with-multi-parents.out.yaml | 2 - ...kendtrafficpolicy-strategic-merge.out.yaml | 2 - .../backendtrafficpolicy-tracing.out.yaml | 1 - ...trafficpolicy-use-client-protocol.out.yaml | 3 +- ...rafficpolicy-with-circuitbreakers.out.yaml | 2 - ...ndtrafficpolicy-with-dns-settings.out.yaml | 3 - ...endtrafficpolicy-with-healthcheck.out.yaml | 7 - .../backendtrafficpolicy-with-http2.out.yaml | 2 - ...fficpolicy-with-httproute-timeout.out.yaml | 4 +- ...ndtrafficpolicy-with-loadbalancer.out.yaml | 4 - ...telimit-default-route-level-limit.out.yaml | 3 +- ...cal-ratelimit-distinct-match-type.out.yaml | 3 +- ...rafficpolicy-with-local-ratelimit.out.yaml | 3 +- ...rafficpolicy-with-panic-threshold.out.yaml | 3 - ...dtrafficpolicy-with-proxyprotocol.out.yaml | 2 - ...ckendtrafficpolicy-with-ratelimit.out.yaml | 4 +- ...fficpolicy-with-response-override.out.yaml | 3 - ...backendtrafficpolicy-with-retries.out.yaml | 2 - ...olicy-with-same-prefix-httproutes.out.yaml | 1 - ...rafficpolicy-with-shared-ratelimit.in.yaml | 5 +- ...afficpolicy-with-shared-ratelimit.out.yaml | 15 +- ...ndtrafficpolicy-with-tcpkeepalive.out.yaml | 2 - ...ficpolicy-with-timeout-targetrefs.out.yaml | 2 - ...backendtrafficpolicy-with-timeout.out.yaml | 2 - ...backendtrafficpolicy-with-timeout.out.yaml | 2 - .../testdata/httproute-retry.out.yaml | 1 - .../merge-with-isolated-policies-2.out.yaml | 2 - .../merge-with-isolated-policies.out.yaml | 1 - internal/ir/xds.go | 24 +- internal/ir/zz_generated.deepcopy.go | 10 +- internal/utils/merge.go | 50 ++ ...dtrafficpolicy_ratelimitrule_merge.in.yaml | 4 +- ...icy_ratelimitrule_merge.jsonmerge.out.yaml | 4 +- ...afficpolicy_ratelimitrule_merge.patch.yaml | 3 +- ...atelimitrule_merge.strategicmerge.out.yaml | 22 +- internal/xds/translator/ratelimit.go | 387 +++++++++------ .../in/ratelimit-config/distinct-match.yaml | 4 +- .../distinct-remote-address-match.yaml | 4 +- .../empty-header-matches.yaml | 4 +- .../global-shared-distinct-match.yaml | 6 +- ...lobal-shared-multiple-shared-policies.yaml | 24 +- .../header-and-cidr-matches.yaml | 4 +- .../masked-remote-address-match.yaml | 4 +- .../in/ratelimit-config/multiple-domains.yaml | 12 +- ...multiple-global-shared-distinct-match.yaml | 47 ++ .../multiple-listeners-distinct-match.yaml | 8 +- ...-listeners-same-shared-distinct-match.yaml | 12 +- ...tiple-listeners-shared-distinct-match.yaml | 11 +- ...d-remote-address-match-with-same-cidr.yaml | 12 +- .../in/ratelimit-config/multiple-matches.yaml | 6 +- .../in/ratelimit-config/multiple-routes.yaml | 8 +- .../in/ratelimit-config/multiple-rules.yaml | 10 +- .../multiple-shared-and-unshared.yaml | 47 ++ .../multiple-shared-ratelimit-rules.yaml | 47 ++ .../in/ratelimit-config/value-match.yaml | 6 +- .../in/xds-ir/ratelimit-global-shared.yaml | 28 +- .../xds-ir/ratelimit-multi-global-shared.yaml | 94 ++++ .../global-shared-distinct-match.yaml | 8 +- ...lobal-shared-multiple-shared-policies.yaml | 16 +- .../ratelimit-config/multiple-domains.yaml | 8 +- ...multiple-global-shared-distinct-match.yaml | 54 +++ ...-listeners-same-shared-distinct-match.yaml | 8 +- ...tiple-listeners-shared-distinct-match.yaml | 8 +- .../multiple-shared-and-unshared.yaml | 69 +++ .../multiple-shared-ratelimit-rules.yaml | 58 +++ .../ratelimit-global-shared.listeners.yaml | 8 +- .../ratelimit-global-shared.routes.yaml | 12 +- ...atelimit-multi-global-shared.clusters.yaml | 105 +++++ ...telimit-multi-global-shared.endpoints.yaml | 36 ++ ...telimit-multi-global-shared.listeners.yaml | 54 +++ .../ratelimit-multi-global-shared.routes.yaml | 79 ++++ site/content/en/latest/api/extension_types.md | 2 +- ...obal-shared-and-unshared-header-match.yaml | 111 +++++ .../ratelimit-global-shared-cidr-match.yaml | 2 +- ...it-global-shared-gateway-header-match.yaml | 2 +- test/e2e/tests/ratelimit.go | 84 ++++ test/helm/gateway-crds-helm/all.out.yaml | 25 +- .../envoy-gateway-crds.out.yaml | 25 +- 99 files changed, 2562 insertions(+), 472 deletions(-) create mode 100644 internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-global-ratelimit.in.yaml create mode 100644 internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-global-ratelimit.out.yaml create mode 100644 internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-local-ratelimit.in.yaml create mode 100644 internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-local-ratelimit.out.yaml create mode 100644 internal/xds/translator/testdata/in/ratelimit-config/multiple-global-shared-distinct-match.yaml create mode 100644 internal/xds/translator/testdata/in/ratelimit-config/multiple-shared-and-unshared.yaml create mode 100644 internal/xds/translator/testdata/in/ratelimit-config/multiple-shared-ratelimit-rules.yaml create mode 100644 internal/xds/translator/testdata/in/xds-ir/ratelimit-multi-global-shared.yaml create mode 100644 internal/xds/translator/testdata/out/ratelimit-config/multiple-global-shared-distinct-match.yaml create mode 100644 internal/xds/translator/testdata/out/ratelimit-config/multiple-shared-and-unshared.yaml create mode 100644 internal/xds/translator/testdata/out/ratelimit-config/multiple-shared-ratelimit-rules.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/ratelimit-multi-global-shared.clusters.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/ratelimit-multi-global-shared.endpoints.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/ratelimit-multi-global-shared.listeners.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/ratelimit-multi-global-shared.routes.yaml create mode 100644 test/e2e/testdata/ratelimit-global-shared-and-unshared-header-match.yaml diff --git a/api/v1alpha1/ratelimit_types.go b/api/v1alpha1/ratelimit_types.go index 21727bb057..d3cd36bca2 100644 --- a/api/v1alpha1/ratelimit_types.go +++ b/api/v1alpha1/ratelimit_types.go @@ -49,19 +49,8 @@ type GlobalRateLimit struct { // matches two rules, one rate limited and one not, the final decision will be // to rate limit the request. // - // +patchMergeKey:"name" - // +patchStrategy:"merge" // +kubebuilder:validation:MaxItems=64 - Rules []RateLimitRule `json:"rules" patchMergeKey:"name" patchStrategy:"merge"` - - // Shared determines whether the rate limit rules apply across all the policy targets. - // If set to true, the rule is treated as a common bucket and is shared across all policy targets (xRoutes). - // Default: false. - // - // +optional - // +notImplementedHide - // +kubebuilder:default=false - Shared *bool `json:"shared,omitempty"` + Rules []RateLimitRule `json:"rules"` } // LocalRateLimit defines local rate limit configuration. @@ -71,23 +60,15 @@ type LocalRateLimit struct { // matches two rules, one with 10rps and one with 20rps, the final limit will // be based on the rule with 10rps. // - // +patchMergeKey:"name" - // +patchStrategy:"merge" - // // +optional // +kubebuilder:validation:MaxItems=16 // +kubebuilder:validation:XValidation:rule="self.all(foo, !has(foo.cost) || !has(foo.cost.response))", message="response cost is not supported for Local Rate Limits" - Rules []RateLimitRule `json:"rules" patchMergeKey:"name" patchStrategy:"merge"` + Rules []RateLimitRule `json:"rules"` } // RateLimitRule defines the semantics for matching attributes // from the incoming requests, and setting limits for them. type RateLimitRule struct { - // Name is the name of the rule. This is used to identify the rule - // in the Envoy configuration and as a unique identifier for merging. - // - // +optional - Name string `json:"name,omitempty"` // 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 @@ -118,6 +99,12 @@ type RateLimitRule struct { // // +optional Cost *RateLimitCost `json:"cost,omitempty"` + // Shared determines whether this rate limit rule applies across all the policy targets. + // If set to true, the rule is treated as a common bucket and is shared across all policy targets (xRoutes). + // Default: false. + // + // +optional + Shared *bool `json:"shared,omitempty"` } type RateLimitCost struct { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index ed3f92b808..87c150497f 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -2853,11 +2853,6 @@ func (in *GlobalRateLimit) DeepCopyInto(out *GlobalRateLimit) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.Shared != nil { - in, out := &in.Shared, &out.Shared - *out = new(bool) - **out = **in - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalRateLimit. @@ -5306,6 +5301,11 @@ func (in *RateLimitRule) DeepCopyInto(out *RateLimitRule) { *out = new(RateLimitCost) (*in).DeepCopyInto(*out) } + if in.Shared != nil { + in, out := &in.Shared, &out.Shared + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitRule. diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml index 66c01520e6..4aeb42b0aa 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml @@ -948,23 +948,17 @@ spec: - requests - unit type: object - name: + shared: description: |- - Name is the name of the rule. This is used to identify the rule - in the Envoy configuration and as a unique identifier for merging. - type: string + Shared determines whether this rate limit rule applies across all the policy targets. + If set to true, the rule is treated as a common bucket and is shared across all policy targets (xRoutes). + Default: false. + type: boolean required: - limit type: object maxItems: 64 type: array - shared: - default: false - description: |- - Shared determines whether the rate limit rules apply across all the policy targets. - If set to true, the rule is treated as a common bucket and is shared across all policy targets (xRoutes). - Default: false. - type: boolean required: - rules type: object @@ -1203,11 +1197,12 @@ spec: - requests - unit type: object - name: + shared: description: |- - Name is the name of the rule. This is used to identify the rule - in the Envoy configuration and as a unique identifier for merging. - type: string + Shared determines whether this rate limit rule applies across all the policy targets. + If set to true, the rule is treated as a common bucket and is shared across all policy targets (xRoutes). + Default: false. + type: boolean required: - limit type: object diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml index af436290d9..2f79f196d2 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml @@ -947,23 +947,17 @@ spec: - requests - unit type: object - name: + shared: description: |- - Name is the name of the rule. This is used to identify the rule - in the Envoy configuration and as a unique identifier for merging. - type: string + Shared determines whether this rate limit rule applies across all the policy targets. + If set to true, the rule is treated as a common bucket and is shared across all policy targets (xRoutes). + Default: false. + type: boolean required: - limit type: object maxItems: 64 type: array - shared: - default: false - description: |- - Shared determines whether the rate limit rules apply across all the policy targets. - If set to true, the rule is treated as a common bucket and is shared across all policy targets (xRoutes). - Default: false. - type: boolean required: - rules type: object @@ -1202,11 +1196,12 @@ spec: - requests - unit type: object - name: + shared: description: |- - Name is the name of the rule. This is used to identify the rule - in the Envoy configuration and as a unique identifier for merging. - type: string + Shared determines whether this rate limit rule applies across all the policy targets. + If set to true, the rule is treated as a common bucket and is shared across all policy targets (xRoutes). + Default: false. + type: boolean required: - limit type: object diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go index 75fcd286a9..b670a2ea28 100644 --- a/internal/gatewayapi/backendtrafficpolicy.go +++ b/internal/gatewayapi/backendtrafficpolicy.go @@ -429,13 +429,33 @@ func (t *Translator) translateBackendTrafficPolicyForRouteWithMerge( if err != nil { return fmt.Errorf("error merging policies: %w", err) } + + // Build traffic features from the merged policy tf, errs := t.buildTrafficFeatures(mergedPolicy, resources) if tf == nil { // should not happen return nil } - // Apply IR to relevant gateway routes + // Since GlobalRateLimit merge relies on IR auto-generated key: (//rule/) + // We can't simply merge the BTP's using utils.Merge() we need to specifically merge the GlobalRateLimit.Rules using IR fields. + // Since ir.TrafficFeatures is not a built-in Kubernetes API object with defined merging strategies and it does not support a deep merge (for lists/maps). + if policy.Spec.RateLimit != nil && gwPolicy.Spec.RateLimit != nil { + tfGW, _ := t.buildTrafficFeatures(gwPolicy, resources) + tfRoute, _ := t.buildTrafficFeatures(policy, resources) + + if tfGW != nil && tfRoute != nil && + tfGW.RateLimit != nil && tfRoute.RateLimit != nil { + + mergedRL, err := utils.MergeRL(tfGW.RateLimit, tfRoute.RateLimit, *policy.Spec.MergeType) + if err != nil { + return fmt.Errorf("error merging rate limits: %w", err) + } + // Replace the rate limit in the merged features if successful + tf.RateLimit = mergedRL + } + } + x, ok := xdsIR[t.IRKey(gatewayNN)] if !ok { // should not happen. @@ -489,7 +509,6 @@ func applyTrafficFeatureToRoute(route RouteContext, } r.Traffic = tf.DeepCopy() - r.Traffic.Name = irTrafficName(policy) if localTo, err := buildClusterSettingsTimeout(policy.Spec.ClusterSettings); err == nil { r.Traffic.Timeout = localTo @@ -695,7 +714,6 @@ func (t *Translator) translateBackendTrafficPolicyForGateway( } r.Traffic = tf.DeepCopy() - r.Traffic.Name = irTrafficName(policy) // Update the Host field in HealthCheck, now that we have access to the Route Hostname. r.Traffic.HealthCheck.SetHTTPHostIfAbsent(r.Hostname) @@ -769,7 +787,7 @@ func (t *Translator) buildLocalRateLimit(policy *egv1a1.BackendTrafficPolicy) (* var err error var irRule *ir.RateLimitRule irRules := make([]*ir.RateLimitRule, 0) - for _, rule := range local.Rules { + for i, rule := range local.Rules { // We don't process the rule without clientSelectors here because it's // previously used as the default route-level limit. if len(rule.ClientSelectors) == 0 { @@ -780,6 +798,8 @@ func (t *Translator) buildLocalRateLimit(policy *egv1a1.BackendTrafficPolicy) (* if err != nil { return nil, err } + // Set the Name field as //rule/ + irRule.Name = irRuleName(policy.Namespace, policy.Name, i) irRules = append(irRules, irRule) } @@ -804,8 +824,7 @@ func (t *Translator) buildGlobalRateLimit(policy *egv1a1.BackendTrafficPolicy) ( global := policy.Spec.RateLimit.Global rateLimit := &ir.RateLimit{ Global: &ir.GlobalRateLimit{ - Rules: make([]*ir.RateLimitRule, len(global.Rules)), - Shared: global.Shared, + Rules: make([]*ir.RateLimitRule, len(global.Rules)), }, } @@ -816,6 +835,8 @@ func (t *Translator) buildGlobalRateLimit(policy *egv1a1.BackendTrafficPolicy) ( if err != nil { return nil, err } + // Set the Name field as //rule/ + irRules[i].Name = irRuleName(policy.Namespace, policy.Name, i) } return rateLimit, nil @@ -828,6 +849,7 @@ func buildRateLimitRule(rule egv1a1.RateLimitRule) (*ir.RateLimitRule, error) { Unit: ir.RateLimitUnit(rule.Limit.Unit), }, HeaderMatches: make([]*ir.StringMatch, 0), + Shared: rule.Shared, } for _, match := range rule.ClientSelectors { diff --git a/internal/gatewayapi/helpers.go b/internal/gatewayapi/helpers.go index 20d9a5663f..81cf59f3fb 100644 --- a/internal/gatewayapi/helpers.go +++ b/internal/gatewayapi/helpers.go @@ -408,6 +408,10 @@ func irDestinationSettingName(destName string, backendIdx int) string { return fmt.Sprintf("%s/backend/%d", destName, backendIdx) } +func irRuleName(policyNamespace, policyName string, ruleIndex int) string { + return fmt.Sprintf("%s/%s/rule/%d", policyNamespace, policyName, ruleIndex) +} + // irTLSConfigs produces a defaulted IR TLSConfig func irTLSConfigs(tlsSecrets ...*corev1.Secret) *ir.TLSConfig { if len(tlsSecrets) == 0 { @@ -450,11 +454,6 @@ func irTLSCACertName(namespace, name string) string { return fmt.Sprintf("%s/%s/%s", namespace, name, caCertKey) } -// Helper function to format the policy name and namespace -func irTrafficName(policy *egv1a1.BackendTrafficPolicy) string { - return fmt.Sprintf("%s/%s", policy.Namespace, policy.Name) -} - func IsMergeGatewaysEnabled(resources *resource.Resources) bool { return resources.EnvoyProxyForGatewayClass != nil && resources.EnvoyProxyForGatewayClass.Spec.MergeGateways != nil && *resources.EnvoyProxyForGatewayClass.Spec.MergeGateways } diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-buffer-limit-out-of-range-error.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-buffer-limit-out-of-range-error.out.yaml index 7a971b9462..8906c3c116 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-buffer-limit-out-of-range-error.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-buffer-limit-out-of-range-error.out.yaml @@ -284,7 +284,6 @@ xdsIR: namespace: default name: grpcroute/default/grpcroute-1/rule/0/match/-1/* traffic: - name: envoy-gateway/policy-for-gateway timeout: http: connectionIdleTimeout: 16s diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-buffer-limit-with-invalid-value.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-buffer-limit-with-invalid-value.out.yaml index 9f79eab20e..55f4c88547 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-buffer-limit-with-invalid-value.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-buffer-limit-with-invalid-value.out.yaml @@ -284,7 +284,6 @@ xdsIR: namespace: default name: grpcroute/default/grpcroute-1/rule/0/match/-1/* traffic: - name: envoy-gateway/policy-for-gateway timeout: http: connectionIdleTimeout: 16s diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-buffer-limit.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-buffer-limit.out.yaml index fb4ae09a9a..b3d716c811 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-buffer-limit.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-buffer-limit.out.yaml @@ -284,7 +284,6 @@ xdsIR: namespace: default name: grpcroute/default/grpcroute-1/rule/0/match/-1/* traffic: - name: envoy-gateway/policy-for-gateway timeout: http: connectionIdleTimeout: 16s @@ -340,7 +339,6 @@ xdsIR: traffic: backendConnection: bufferLimit: 100000000 - name: default/policy-for-route readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-compression.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-compression.out.yaml index aa1385507a..df89978b39 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-compression.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-compression.out.yaml @@ -171,7 +171,6 @@ xdsIR: compression: - type: Brotli - type: Gzip - name: default/policy-for-route readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-dns-lookup-family.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-dns-lookup-family.out.yaml index a7aba41328..0db7d5ab16 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-dns-lookup-family.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-dns-lookup-family.out.yaml @@ -449,7 +449,6 @@ xdsIR: dnsRefreshRate: 5s lookupFamily: IPv6 respectDnsTtl: false - name: default/backend-traffic-policy - destination: name: grpcroute/default/grpcroute-1/rule/0 settings: @@ -495,7 +494,6 @@ xdsIR: dnsRefreshRate: 5s lookupFamily: IPv6 respectDnsTtl: false - name: default/backend-traffic-policy readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-spdy.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-spdy.out.yaml index 689cb9c294..1903430df1 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-spdy.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-spdy.out.yaml @@ -169,7 +169,6 @@ xdsIR: traffic: httpUpgrade: - spdy/3.1 - name: default/policy-for-route readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-websocket.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-websocket.out.yaml index 0cefe0206d..5e43ac6652 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-websocket.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-http-upgrade-websocket.out.yaml @@ -171,7 +171,6 @@ xdsIR: httpUpgrade: - websocket - spdy/3.1 - name: default/policy-for-route readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-override-replace.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-override-replace.out.yaml index e43484d457..ad527fceec 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-override-replace.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-override-replace.out.yaml @@ -313,7 +313,6 @@ xdsIR: loadBalancer: consistentHash: sourceIP: true - name: default/policy-for-route-1 - destination: name: httproute/default/httproute-2/rule/0 settings: @@ -338,7 +337,6 @@ xdsIR: traffic: loadBalancer: random: {} - name: envoy-gateway/policy-for-gateway-1 timeout: http: connectionIdleTimeout: 21s @@ -366,8 +364,7 @@ xdsIR: distinct: false name: "" prefix: /baz - traffic: - name: default/policy-for-route-3 + traffic: {} readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-request-buffer.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-request-buffer.out.yaml index 4b8fd42178..4b0955aab6 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-request-buffer.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-request-buffer.out.yaml @@ -288,7 +288,6 @@ xdsIR: name: "" prefix: /foo traffic: - name: envoy-gateway/policy-for-gateway requestBuffer: limit: 4Mi readyListener: @@ -338,7 +337,6 @@ xdsIR: name: "" prefix: /foo traffic: - name: default/policy-for-route requestBuffer: limit: 4Mi readyListener: diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-status-conditions.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-status-conditions.out.yaml index fdefe01e1e..daf9e5d208 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-status-conditions.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-status-conditions.out.yaml @@ -584,8 +584,7 @@ xdsIR: distinct: false name: "" prefix: / - traffic: - name: envoy-gateway/target-httproute-in-gateway-1 + traffic: {} readyListener: address: 0.0.0.0 ipFamily: IPv4 @@ -632,8 +631,7 @@ xdsIR: name: grpcroute-1 namespace: envoy-gateway name: grpcroute/envoy-gateway/grpcroute-1/rule/0/match/0/* - traffic: - name: envoy-gateway/target-grpcroute-in-gateway-2 + traffic: {} readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-status-fault-injection.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-status-fault-injection.out.yaml index a180cead0b..41e2c5a0cc 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-status-fault-injection.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-status-fault-injection.out.yaml @@ -371,7 +371,6 @@ xdsIR: delay: fixedDelay: 5.4s percentage: 80 - name: default/policy-for-grpcroute readyListener: address: 0.0.0.0 ipFamily: IPv4 @@ -423,7 +422,6 @@ xdsIR: abort: httpStatus: 14 percentage: 0.01 - name: envoy-gateway/policy-for-gateway - destination: name: httproute/default/httproute-1/rule/0 settings: @@ -453,7 +451,6 @@ xdsIR: delay: fixedDelay: 5.4s percentage: 80 - name: default/policy-for-route readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-global-ratelimit.in.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-global-ratelimit.in.yaml new file mode 100644 index 0000000000..585a70dc01 --- /dev/null +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-global-ratelimit.in.yaml @@ -0,0 +1,115 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All + - apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-2 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +httpRoutes: + - apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + - namespace: envoy-gateway + name: gateway-2 + sectionName: http + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 +backendTrafficPolicies: + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: envoy-gateway + name: policy-for-gateway1 + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + timeout: + tcp: + connectTimeout: 15s + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + httpUpgrade: + - type: websocket + rateLimit: + type: Global + global: + rules: + - clientSelectors: + - headers: + - name: x-user-id + type: Exact + value: two + limit: + requests: 5 + unit: Hour + shared: true + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: default + name: policy-for-route + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + mergeType: StrategicMerge + timeout: + tcp: + connectTimeout: 10s + connection: + bufferLimit: 100M + httpUpgrade: + - type: "spdy/3.1" + rateLimit: + type: Global + global: + rules: + - clientSelectors: + - headers: + - name: x-user-id + type: Exact + value: one + limit: + requests: 5 + unit: Hour + shared: true diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-global-ratelimit.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-global-ratelimit.out.yaml new file mode 100644 index 0000000000..c70c9d724a --- /dev/null +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-global-ratelimit.out.yaml @@ -0,0 +1,443 @@ +backendTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + creationTimestamp: null + name: policy-for-route + namespace: default + spec: + connection: + bufferLimit: 100M + httpUpgrade: + - type: spdy/3.1 + mergeType: StrategicMerge + rateLimit: + global: + rules: + - clientSelectors: + - headers: + - name: x-user-id + type: Exact + value: one + limit: + requests: 5 + unit: Hour + shared: true + type: Global + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + timeout: + tcp: + connectTimeout: 10s + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Merged with policy envoy-gateway/policy-for-gateway1 + reason: Merged + status: "True" + type: Merged + controllerName: gateway.envoyproxy.io/gatewayclass-controller + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-2 + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + creationTimestamp: null + name: policy-for-gateway1 + namespace: envoy-gateway + spec: + httpUpgrade: + - type: websocket + rateLimit: + global: + rules: + - clientSelectors: + - headers: + - name: x-user-id + type: Exact + value: two + limit: + requests: 5 + unit: Hour + shared: true + type: Global + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + timeout: + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + tcp: + connectTimeout: 15s + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: 'This policy is being merged by other backendTrafficPolicies for + these routes: [default/httproute-1]' + reason: Merged + status: "True" + type: Merged + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-2 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + - name: gateway-2 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-2 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system + envoy-gateway/gateway-2: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-2/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-2 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-2 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + traffic: + backendConnection: + bufferLimit: 100000000 + httpUpgrade: + - spdy/3.1 + - websocket + rateLimit: + global: + rules: + - headerMatches: + - distinct: false + exact: one + name: x-user-id + limit: + requests: 5 + unit: Hour + name: default/policy-for-route/rule/0 + shared: true + - headerMatches: + - distinct: false + exact: two + name: x-user-id + limit: + requests: 5 + unit: Hour + name: envoy-gateway/policy-for-gateway1/rule/0 + shared: true + timeout: + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + tcp: + connectTimeout: 10s + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 + envoy-gateway/gateway-2: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-2 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-2/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + traffic: + backendConnection: + bufferLimit: 100000000 + httpUpgrade: + - spdy/3.1 + rateLimit: + global: + rules: + - headerMatches: + - distinct: false + exact: one + name: x-user-id + limit: + requests: 5 + unit: Hour + name: default/policy-for-route/rule/0 + shared: true + timeout: + tcp: + connectTimeout: 10s + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-local-ratelimit.in.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-local-ratelimit.in.yaml new file mode 100644 index 0000000000..3a9c8ccf77 --- /dev/null +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-local-ratelimit.in.yaml @@ -0,0 +1,113 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All + - apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-2 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +httpRoutes: + - apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + - namespace: envoy-gateway + name: gateway-2 + sectionName: http + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 +backendTrafficPolicies: + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: envoy-gateway + name: policy-for-gateway1 + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + timeout: + tcp: + connectTimeout: 15s + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + httpUpgrade: + - type: websocket + rateLimit: + type: Local + local: + rules: + - clientSelectors: + - headers: + - name: x-user-id + type: Exact + value: two + limit: + requests: 5 + unit: Hour + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: default + name: policy-for-route + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + mergeType: StrategicMerge + timeout: + tcp: + connectTimeout: 10s + connection: + bufferLimit: 100M + httpUpgrade: + - type: "spdy/3.1" + rateLimit: + type: Local + local: + rules: + - clientSelectors: + - headers: + - name: x-user-id + type: Exact + value: one + limit: + requests: 5 + unit: Hour diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-local-ratelimit.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-local-ratelimit.out.yaml new file mode 100644 index 0000000000..e0eb784ada --- /dev/null +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-local-ratelimit.out.yaml @@ -0,0 +1,444 @@ +backendTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + creationTimestamp: null + name: policy-for-route + namespace: default + spec: + connection: + bufferLimit: 100M + httpUpgrade: + - type: spdy/3.1 + mergeType: StrategicMerge + rateLimit: + local: + rules: + - clientSelectors: + - headers: + - name: x-user-id + type: Exact + value: one + limit: + requests: 5 + unit: Hour + type: Local + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + timeout: + tcp: + connectTimeout: 10s + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Merged with policy envoy-gateway/policy-for-gateway1 + reason: Merged + status: "True" + type: Merged + controllerName: gateway.envoyproxy.io/gatewayclass-controller + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-2 + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + creationTimestamp: null + name: policy-for-gateway1 + namespace: envoy-gateway + spec: + httpUpgrade: + - type: websocket + rateLimit: + local: + rules: + - clientSelectors: + - headers: + - name: x-user-id + type: Exact + value: two + limit: + requests: 5 + unit: Hour + type: Local + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + timeout: + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + tcp: + connectTimeout: 15s + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: 'This policy is being merged by other backendTrafficPolicies for + these routes: [default/httproute-1]' + reason: Merged + status: "True" + type: Merged + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-2 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + - name: gateway-2 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-2 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system + envoy-gateway/gateway-2: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-2/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-2 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-2 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + traffic: + backendConnection: + bufferLimit: 100000000 + httpUpgrade: + - spdy/3.1 + - websocket + rateLimit: + local: + default: + requests: 4294967295 + unit: Second + rules: + - headerMatches: + - distinct: false + exact: one + name: x-user-id + limit: + requests: 5 + unit: Hour + name: default/policy-for-route/rule/0 + - headerMatches: + - distinct: false + exact: two + name: x-user-id + limit: + requests: 5 + unit: Hour + name: envoy-gateway/policy-for-gateway1/rule/0 + timeout: + http: + connectionIdleTimeout: 16s + maxConnectionDuration: 17s + tcp: + connectTimeout: 10s + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 + envoy-gateway/gateway-2: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-2 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-2/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / + traffic: + backendConnection: + bufferLimit: 100000000 + httpUpgrade: + - spdy/3.1 + rateLimit: + local: + default: + requests: 4294967295 + unit: Second + rules: + - headerMatches: + - distinct: false + exact: one + name: x-user-id + limit: + requests: 5 + unit: Hour + name: default/policy-for-route/rule/0 + timeout: + tcp: + connectTimeout: 10s + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-with-multi-parents.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-with-multi-parents.out.yaml index 70ff8aee37..1c466803e1 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-with-multi-parents.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge-with-multi-parents.out.yaml @@ -317,7 +317,6 @@ xdsIR: httpUpgrade: - spdy/3.1 - websocket - name: default/policy-for-route timeout: http: connectionIdleTimeout: 16s @@ -375,7 +374,6 @@ xdsIR: bufferLimit: 100000000 httpUpgrade: - spdy/3.1 - name: default/policy-for-route timeout: tcp: connectTimeout: 10s diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge.out.yaml index 8905ab639d..f7cdccc6c8 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-strategic-merge.out.yaml @@ -297,7 +297,6 @@ xdsIR: httpUpgrade: - spdy/3.1 - websocket - name: default/merged-policy-for-route timeout: http: connectionIdleTimeout: 16s @@ -324,7 +323,6 @@ xdsIR: traffic: backendConnection: bufferLimit: 100000000 - name: default/policy-for-route readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-tracing.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-tracing.out.yaml index 03a2b28d43..c1996019f4 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-tracing.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-tracing.out.yaml @@ -185,7 +185,6 @@ xdsIR: name: "" prefix: / traffic: - name: default/policy-for-route telemetry: tracing: customTags: diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-use-client-protocol.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-use-client-protocol.out.yaml index c03c4cbc11..d3cb4bdd73 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-use-client-protocol.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-use-client-protocol.out.yaml @@ -164,8 +164,7 @@ xdsIR: distinct: false name: "" prefix: / - traffic: - name: envoy-gateway/policy-for-gateway + traffic: {} useClientProtocol: true readyListener: address: 0.0.0.0 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-circuitbreakers.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-circuitbreakers.out.yaml index 34f11b574a..919e9fdb8e 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-circuitbreakers.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-circuitbreakers.out.yaml @@ -294,7 +294,6 @@ xdsIR: maxParallelRetries: 1024 maxPendingRequests: 1 maxRequestsPerConnection: 1 - name: envoy-gateway/policy-for-gateway readyListener: address: 0.0.0.0 ipFamily: IPv4 @@ -348,7 +347,6 @@ xdsIR: maxParallelRetries: 24 maxPendingRequests: 42 maxRequestsPerConnection: 42 - name: default/policy-for-route readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-dns-settings.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-dns-settings.out.yaml index c92652e8d4..d42ca5614c 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-dns-settings.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-dns-settings.out.yaml @@ -360,7 +360,6 @@ xdsIR: dns: dnsRefreshRate: 10s respectDnsTtl: true - name: envoy-gateway/policy-for-all-routes-in-gateway-1 readyListener: address: 0.0.0.0 ipFamily: IPv4 @@ -411,7 +410,6 @@ xdsIR: dns: dnsRefreshRate: 5s respectDnsTtl: false - name: default/policy-for-route-2 - destination: name: httproute/default/httproute-1/rule/0 settings: @@ -437,7 +435,6 @@ xdsIR: dns: dnsRefreshRate: 1s respectDnsTtl: true - name: default/policy-for-route-1 readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-healthcheck.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-healthcheck.out.yaml index 75d73b9a78..96dff6b971 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-healthcheck.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-healthcheck.out.yaml @@ -736,7 +736,6 @@ xdsIR: interval: 2s maxEjectionPercent: 100 splitExternalLocalOriginErrors: false - name: envoy-gateway/policy-for-gateway - destination: name: grpcroute/default/grpcroute-3/rule/0 settings: @@ -763,7 +762,6 @@ xdsIR: interval: 3s timeout: 1s unhealthyThreshold: 3 - name: default/policy-for-grpc-route-3 - destination: name: grpcroute/default/grpcroute-2/rule/0 settings: @@ -789,7 +787,6 @@ xdsIR: interval: 3s timeout: 1s unhealthyThreshold: 3 - name: default/policy-for-grpc-route readyListener: address: 0.0.0.0 ipFamily: IPv4 @@ -856,7 +853,6 @@ xdsIR: interval: 10s maxEjectionPercent: 10 splitExternalLocalOriginErrors: false - name: default/policy-for-route-2 - destination: name: httproute/default/httproute-3/rule/0 settings: @@ -898,7 +894,6 @@ xdsIR: interval: 8ms maxEjectionPercent: 11 splitExternalLocalOriginErrors: false - name: default/policy-for-route-3 - destination: name: httproute/default/httproute-4/rule/0 settings: @@ -935,7 +930,6 @@ xdsIR: interval: 5s timeout: 1s unhealthyThreshold: 3 - name: default/policy-for-route-4 - destination: name: httproute/default/httproute-1/rule/0 settings: @@ -981,7 +975,6 @@ xdsIR: interval: 1s maxEjectionPercent: 100 splitExternalLocalOriginErrors: false - name: default/policy-for-route-1 readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-http2.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-http2.out.yaml index a353466758..9b75868372 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-http2.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-http2.out.yaml @@ -291,7 +291,6 @@ xdsIR: initialStreamWindowSize: 1073741824 maxConcurrentStreams: 500 resetStreamOnError: false - name: envoy-gateway/policy-for-gateway readyListener: address: 0.0.0.0 ipFamily: IPv4 @@ -344,7 +343,6 @@ xdsIR: initialStreamWindowSize: 524288000 maxConcurrentStreams: 200 resetStreamOnError: true - name: default/policy-for-route readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-httproute-timeout.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-httproute-timeout.out.yaml index d0285325ea..de18621593 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-httproute-timeout.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-httproute-timeout.out.yaml @@ -246,8 +246,7 @@ xdsIR: name: "" prefix: / timeout: 2m10s - traffic: - name: default/policy-for-http-route-1 + traffic: {} useClientProtocol: true - destination: name: httproute/default/httproute-2/rule/0 @@ -276,7 +275,6 @@ xdsIR: consistentHash: cookie: name: test - name: envoy-gateway/policy-for-gateway useClientProtocol: true readyListener: address: 0.0.0.0 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-loadbalancer.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-loadbalancer.out.yaml index 2b69a9babc..3a5678c79b 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-loadbalancer.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-loadbalancer.out.yaml @@ -458,7 +458,6 @@ xdsIR: traffic: loadBalancer: random: {} - name: envoy-gateway/policy-for-gateway readyListener: address: 0.0.0.0 ipFamily: IPv4 @@ -510,7 +509,6 @@ xdsIR: leastRequest: slowStart: window: 5m0s - name: default/policy-for-route2 - destination: name: httproute/default/httproute-3/rule/0 settings: @@ -537,7 +535,6 @@ xdsIR: consistentHash: cookie: name: test - name: default/policy-for-route3 - destination: name: httproute/default/httproute-1/rule/0 settings: @@ -564,7 +561,6 @@ xdsIR: consistentHash: sourceIP: true tableSize: 524287 - name: default/policy-for-route readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit-default-route-level-limit.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit-default-route-level-limit.out.yaml index 6faf1dd119..fffa3d8ef9 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit-default-route-level-limit.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit-default-route-level-limit.out.yaml @@ -188,7 +188,6 @@ xdsIR: name: "" prefix: / traffic: - name: envoy-gateway/policy-for-gateway rateLimit: local: default: @@ -205,6 +204,7 @@ xdsIR: limit: requests: 10 unit: Hour + name: envoy-gateway/policy-for-gateway/rule/0 - cidrMatch: cidr: 192.168.0.0/16 distinct: false @@ -221,6 +221,7 @@ xdsIR: limit: requests: 10 unit: Minute + name: envoy-gateway/policy-for-gateway/rule/1 readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit-distinct-match-type.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit-distinct-match-type.out.yaml index ad18d64714..bb901670be 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit-distinct-match-type.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit-distinct-match-type.out.yaml @@ -188,7 +188,6 @@ xdsIR: name: "" prefix: / traffic: - name: envoy-gateway/policy-for-gateway rateLimit: local: default: @@ -204,6 +203,7 @@ xdsIR: limit: requests: 10 unit: Hour + name: envoy-gateway/policy-for-gateway/rule/0 - cidrMatch: cidr: 192.168.0.0/16 distinct: false @@ -220,6 +220,7 @@ xdsIR: limit: requests: 10 unit: Minute + name: envoy-gateway/policy-for-gateway/rule/1 readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit.out.yaml index 1f70bc3c26..7509c84446 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-local-ratelimit.out.yaml @@ -191,7 +191,6 @@ xdsIR: name: "" prefix: / traffic: - name: envoy-gateway/policy-for-gateway rateLimit: local: default: @@ -208,6 +207,7 @@ xdsIR: limit: requests: 10 unit: Hour + name: envoy-gateway/policy-for-gateway/rule/1 - cidrMatch: cidr: 192.168.0.0/16 distinct: false @@ -224,6 +224,7 @@ xdsIR: limit: requests: 10 unit: Minute + name: envoy-gateway/policy-for-gateway/rule/2 readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-panic-threshold.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-panic-threshold.out.yaml index 5054a68777..cb30f5b23c 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-panic-threshold.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-panic-threshold.out.yaml @@ -356,7 +356,6 @@ xdsIR: traffic: healthCheck: panicThreshold: 80 - name: envoy-gateway/policy-for-all-routes-in-gateway-1 readyListener: address: 0.0.0.0 ipFamily: IPv4 @@ -406,7 +405,6 @@ xdsIR: traffic: healthCheck: panicThreshold: 10 - name: default/policy-for-route-2 - destination: name: httproute/default/httproute-1/rule/0 settings: @@ -431,7 +429,6 @@ xdsIR: traffic: healthCheck: panicThreshold: 66 - name: default/policy-for-route-1 readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-proxyprotocol.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-proxyprotocol.out.yaml index dfadd197e0..45e87169d4 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-proxyprotocol.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-proxyprotocol.out.yaml @@ -280,7 +280,6 @@ xdsIR: namespace: default name: grpcroute/default/grpcroute-1/rule/0/match/-1/* traffic: - name: envoy-gateway/policy-for-gateway proxyProtocol: version: V1 readyListener: @@ -330,7 +329,6 @@ xdsIR: name: "" prefix: / traffic: - name: default/policy-for-route proxyProtocol: version: V2 readyListener: diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml index c0c8debf20..435096820c 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-ratelimit.out.yaml @@ -312,7 +312,6 @@ xdsIR: namespace: default name: grpcroute/default/grpcroute-1/rule/0/match/-1/* traffic: - name: envoy-gateway/policy-for-gateway rateLimit: global: rules: @@ -329,6 +328,7 @@ xdsIR: limit: requests: 10 unit: Hour + name: envoy-gateway/policy-for-gateway/rule/0 readyListener: address: 0.0.0.0 ipFamily: IPv4 @@ -376,7 +376,6 @@ xdsIR: name: "" prefix: / traffic: - name: default/policy-for-route rateLimit: global: rules: @@ -390,6 +389,7 @@ xdsIR: limit: requests: 20 unit: Hour + name: default/policy-for-route/rule/0 requestCost: number: 1 responseCost: diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-response-override.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-response-override.out.yaml index c7cc354afc..15c062a97b 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-response-override.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-response-override.out.yaml @@ -399,7 +399,6 @@ xdsIR: namespace: default name: grpcroute/default/grpcroute-1/rule/0/match/-1/* traffic: - name: default/policy-for-gateway responseOverride: name: backendtrafficpolicy/default/policy-for-gateway rules: @@ -470,7 +469,6 @@ xdsIR: name: "" prefix: / traffic: - name: default/policy-for-route-1 responseOverride: name: backendtrafficpolicy/default/policy-for-route-1 rules: @@ -516,7 +514,6 @@ xdsIR: name: "" prefix: / traffic: - name: default/policy-for-route-2 responseOverride: name: backendtrafficpolicy/default/policy-for-route-2 rules: diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-retries.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-retries.out.yaml index 5b1f96de97..5785fcf001 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-retries.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-retries.out.yaml @@ -436,7 +436,6 @@ xdsIR: namespace: default name: grpcroute/default/grpcroute-1/rule/0/match/-1/* traffic: - name: envoy-gateway/policy-for-gateway retry: perRetry: backOff: @@ -539,7 +538,6 @@ xdsIR: name: "" prefix: / traffic: - name: default/policy-for-route-1 retry: numRetries: 5 perRetry: diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-same-prefix-httproutes.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-same-prefix-httproutes.out.yaml index a449e2ea13..d2ae6de9a4 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-same-prefix-httproutes.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-same-prefix-httproutes.out.yaml @@ -211,7 +211,6 @@ xdsIR: maxConnections: 2048 maxParallelRequests: 4294967295 maxPendingRequests: 1 - name: default/policy-for-httproute-1 - destination: name: httproute/default/httproute-1-1/rule/0 settings: diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-shared-ratelimit.in.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-shared-ratelimit.in.yaml index 16513852d3..3386225ff7 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-shared-ratelimit.in.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-shared-ratelimit.in.yaml @@ -76,7 +76,6 @@ backendTrafficPolicies: rateLimit: type: Global global: - shared: true rules: - clientSelectors: - headers: @@ -90,6 +89,7 @@ backendTrafficPolicies: limit: requests: 10 unit: Hour + shared: true - apiVersion: gateway.envoyproxy.io/v1alpha1 kind: BackendTrafficPolicy metadata: @@ -103,7 +103,6 @@ backendTrafficPolicies: rateLimit: type: Global global: - shared: true rules: - clientSelectors: - sourceCIDR: @@ -112,6 +111,7 @@ backendTrafficPolicies: limit: requests: 20 unit: Hour + shared: true - clientSelectors: - sourceCIDR: type: "Distinct" @@ -119,3 +119,4 @@ backendTrafficPolicies: limit: requests: 20 unit: Hour + shared: true diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-shared-ratelimit.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-shared-ratelimit.out.yaml index 2ff5ac3922..90a365156f 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-shared-ratelimit.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-shared-ratelimit.out.yaml @@ -16,6 +16,7 @@ backendTrafficPolicies: limit: requests: 20 unit: Hour + shared: true - clientSelectors: - sourceCIDR: type: Distinct @@ -23,7 +24,7 @@ backendTrafficPolicies: limit: requests: 20 unit: Hour - shared: true + shared: true type: Global targetRef: group: gateway.networking.k8s.io @@ -66,7 +67,7 @@ backendTrafficPolicies: limit: requests: 10 unit: Hour - shared: true + shared: true type: Global targetRef: group: gateway.networking.k8s.io @@ -312,7 +313,6 @@ xdsIR: namespace: default name: grpcroute/default/grpcroute-1/rule/0/match/-1/* traffic: - name: envoy-gateway/policy-for-gateway rateLimit: global: rules: @@ -329,7 +329,8 @@ xdsIR: limit: requests: 10 unit: Hour - shared: true + name: envoy-gateway/policy-for-gateway/rule/0 + shared: true readyListener: address: 0.0.0.0 ipFamily: IPv4 @@ -377,7 +378,6 @@ xdsIR: name: "" prefix: / traffic: - name: default/policy-for-route rateLimit: global: rules: @@ -391,6 +391,8 @@ xdsIR: limit: requests: 20 unit: Hour + name: default/policy-for-route/rule/0 + shared: true - cidrMatch: cidr: ::/64 distinct: true @@ -401,7 +403,8 @@ xdsIR: limit: requests: 20 unit: Hour - shared: true + name: default/policy-for-route/rule/1 + shared: true readyListener: address: 0.0.0.0 ipFamily: IPv4 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-tcpkeepalive.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-tcpkeepalive.out.yaml index f614c6eb96..7c9c2587eb 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-tcpkeepalive.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-tcpkeepalive.out.yaml @@ -284,7 +284,6 @@ xdsIR: namespace: default name: grpcroute/default/grpcroute-1/rule/0/match/-1/* traffic: - name: envoy-gateway/policy-for-gateway tcpKeepalive: idleTime: 1200 interval: 60 @@ -336,7 +335,6 @@ xdsIR: name: "" prefix: / traffic: - name: default/policy-for-route tcpKeepalive: idleTime: 10 interval: 1800 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-timeout-targetrefs.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-timeout-targetrefs.out.yaml index eb123fd100..d8a27d8b07 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-timeout-targetrefs.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-timeout-targetrefs.out.yaml @@ -272,7 +272,6 @@ xdsIR: namespace: envoy-gateway name: grpcroute/envoy-gateway/grpcroute-1/rule/0/match/-1/* traffic: - name: envoy-gateway/policy-for-gateway timeout: http: connectionIdleTimeout: 16s @@ -326,7 +325,6 @@ xdsIR: name: "" prefix: / traffic: - name: envoy-gateway/policy-for-gateway timeout: http: connectionIdleTimeout: 16s diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-timeout.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-timeout.out.yaml index 40e234b1f6..f567963c6a 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-timeout.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-timeout.out.yaml @@ -292,7 +292,6 @@ xdsIR: namespace: default name: grpcroute/default/grpcroute-1/rule/0/match/-1/* traffic: - name: envoy-gateway/policy-for-gateway timeout: http: connectionIdleTimeout: 16s @@ -348,7 +347,6 @@ xdsIR: prefix: / timeout: 1s traffic: - name: default/policy-for-route timeout: http: connectionIdleTimeout: 21s diff --git a/internal/gatewayapi/testdata/httproute-and-backendtrafficpolicy-with-timeout.out.yaml b/internal/gatewayapi/testdata/httproute-and-backendtrafficpolicy-with-timeout.out.yaml index 5dffce1c94..3b14a31d9f 100644 --- a/internal/gatewayapi/testdata/httproute-and-backendtrafficpolicy-with-timeout.out.yaml +++ b/internal/gatewayapi/testdata/httproute-and-backendtrafficpolicy-with-timeout.out.yaml @@ -289,7 +289,6 @@ xdsIR: namespace: default name: grpcroute/default/grpcroute-1/rule/0/match/-1/* traffic: - name: envoy-gateway/policy-for-gateway timeout: http: connectionIdleTimeout: 16s @@ -344,7 +343,6 @@ xdsIR: prefix: / timeout: 1s traffic: - name: default/policy-for-route timeout: http: maxConnectionDuration: 22s diff --git a/internal/gatewayapi/testdata/httproute-retry.out.yaml b/internal/gatewayapi/testdata/httproute-retry.out.yaml index d252da5b59..2d9e06df3a 100644 --- a/internal/gatewayapi/testdata/httproute-retry.out.yaml +++ b/internal/gatewayapi/testdata/httproute-retry.out.yaml @@ -310,7 +310,6 @@ xdsIR: - 500 timeout: 3s traffic: - name: default/policy-for-route retry: numRetries: 5 perRetry: diff --git a/internal/gatewayapi/testdata/merge-with-isolated-policies-2.out.yaml b/internal/gatewayapi/testdata/merge-with-isolated-policies-2.out.yaml index 4d416250bc..4a93a03edb 100644 --- a/internal/gatewayapi/testdata/merge-with-isolated-policies-2.out.yaml +++ b/internal/gatewayapi/testdata/merge-with-isolated-policies-2.out.yaml @@ -555,7 +555,6 @@ xdsIR: - x-header-8 maxAge: 33m20s traffic: - name: default/policy-for-gateway tcpKeepalive: idleTime: 1200 interval: 60 @@ -616,7 +615,6 @@ xdsIR: - x-header-8 maxAge: 33m20s traffic: - name: default/policy-for-gateway tcpKeepalive: idleTime: 1200 interval: 60 diff --git a/internal/gatewayapi/testdata/merge-with-isolated-policies.out.yaml b/internal/gatewayapi/testdata/merge-with-isolated-policies.out.yaml index 501ed5795c..b56dfd2852 100644 --- a/internal/gatewayapi/testdata/merge-with-isolated-policies.out.yaml +++ b/internal/gatewayapi/testdata/merge-with-isolated-policies.out.yaml @@ -347,7 +347,6 @@ xdsIR: - x-header-8 maxAge: 33m20s traffic: - name: default/policy-for-gateway tcpKeepalive: idleTime: 1200 interval: 60 diff --git a/internal/ir/xds.go b/internal/ir/xds.go index 777e33eb9c..30bd1d1fcb 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -835,8 +835,6 @@ type Compression struct { // TrafficFeatures holds the information associated with the Backend Traffic Policy. // +k8s:deepcopy-gen=true type TrafficFeatures struct { - // Name of the backend traffic policy and namespace - Name string `json:"name,omitempty"` // RateLimit defines the more specific match conditions as well as limits for ratelimiting // the requests on this route. RateLimit *RateLimit `json:"rateLimit,omitempty" yaml:"rateLimit,omitempty"` @@ -2093,16 +2091,7 @@ type GlobalRateLimit struct { // TODO zhaohuabing: add default values for Global rate limiting. // Rules for rate limiting. - Rules []*RateLimitRule `json:"rules,omitempty" yaml:"rules,omitempty"` - - // Shared determines whether this rate limit rule applies globally across the gateway, or xRoute(s). - // If set to true, the rule is treated as a common bucket and is shared across all routes under the backend traffic policy. - // Must have targetRef set to Gateway or xRoute(s). - // Default: false. - // - // +optional - // +kubebuilder:default=false - Shared *bool `json:"shared,omitempty" yaml:"shared,omitempty"` + Rules []*RateLimitRule `json:"rules,omitempty" yaml:"rules,omitempty" patchStrategy:"merge" patchMergeKey:"name"` } // LocalRateLimit holds the local rate limiting configuration. @@ -2113,7 +2102,7 @@ type LocalRateLimit struct { Default RateLimitValue `json:"default,omitempty" yaml:"default,omitempty"` // Rules for rate limiting. - Rules []*RateLimitRule `json:"rules,omitempty" yaml:"rules,omitempty"` + Rules []*RateLimitRule `json:"rules,omitempty" yaml:"rules,omitempty" patchStrategy:"merge" patchMergeKey:"name"` } // RateLimitRule holds the match and limit configuration for ratelimiting. @@ -2129,6 +2118,15 @@ type RateLimitRule struct { RequestCost *RateLimitCost `json:"requestCost,omitempty" yaml:"requestCost,omitempty"` // ResponseCost specifies the cost of the response. ResponseCost *RateLimitCost `json:"responseCost,omitempty" yaml:"responseCost,omitempty"` + // Shared determines whether this rate limit rule applies globally across the gateway, or xRoute(s). + // If set to true, the rule is treated as a common bucket and is shared across all routes under the backend traffic policy. + // Must have targetRef set to Gateway or xRoute(s). + // Default: false. + // + // +optional + Shared *bool `json:"shared,omitempty" yaml:"shared,omitempty"` + // Name is a unique identifier for this rule, set as //rule/. + Name string `json:"name,omitempty" yaml:"name,omitempty"` } // RateLimitCost specifies the cost of the request or response. diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index 5b27f1e48f..eb5f63edb8 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -1306,11 +1306,6 @@ func (in *GlobalRateLimit) DeepCopyInto(out *GlobalRateLimit) { } } } - if in.Shared != nil { - in, out := &in.Shared, &out.Shared - *out = new(bool) - **out = **in - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlobalRateLimit. @@ -2753,6 +2748,11 @@ func (in *RateLimitRule) DeepCopyInto(out *RateLimitRule) { *out = new(RateLimitCost) (*in).DeepCopyInto(*out) } + if in.Shared != nil { + in, out := &in.Shared, &out.Shared + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitRule. diff --git a/internal/utils/merge.go b/internal/utils/merge.go index 337147ed01..2c02230906 100644 --- a/internal/utils/merge.go +++ b/internal/utils/merge.go @@ -14,6 +14,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/envoyproxy/gateway/internal/ir" ) func MergeWithPatch[T client.Object](original T, patch *egv1a1.KubernetesPatchSpec) (T, error) { @@ -77,3 +78,52 @@ func Merge[T client.Object](original, patch T, mergeType egv1a1.MergeType) (T, e } return mergeInternal(original, patchJSON, mergeType) } + +func mergeRLInternal[T *ir.RateLimit](original T, patchJSON []byte, mergeType egv1a1.MergeType) (T, error) { + var ( + patchedJSON []byte + originalJSON []byte + err error + empty T + ) + + originalJSON, err = json.Marshal(original) + if err != nil { + return empty, fmt.Errorf("error marshaling original service: %w", err) + } + switch mergeType { + case egv1a1.StrategicMerge: + patchedJSON, err = strategicpatch.StrategicMergePatch(originalJSON, patchJSON, empty) + if err != nil { + return empty, fmt.Errorf("error during strategic merge: %w", err) + } + case egv1a1.JSONMerge: + patchedJSON, err = jsonpatch.MergePatch(originalJSON, patchJSON) + if err != nil { + return empty, fmt.Errorf("error during JSON merge: %w", err) + } + default: + return empty, fmt.Errorf("unsupported merge type: %v", mergeType) + } + + res := new(T) + if err = json.Unmarshal(patchedJSON, res); err != nil { + return empty, fmt.Errorf("error unmarshaling patched service: %w", err) + } + + return *res, nil +} + +func MergeRL[T *ir.RateLimit](original, patch T, mergeType egv1a1.MergeType) (T, error) { + var ( + patchJSON []byte + err error + empty T + ) + + patchJSON, err = json.Marshal(patch) + if err != nil { + return empty, fmt.Errorf("error marshaling original service: %w", err) + } + return mergeRLInternal(original, patchJSON, mergeType) +} diff --git a/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.in.yaml b/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.in.yaml index 3dc8eee659..3bf35dc887 100644 --- a/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.in.yaml +++ b/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.in.yaml @@ -1,12 +1,12 @@ kind: BackendTrafficPolicy metadata: - name: original + name: original-1 spec: rateLimit: type: Global global: rules: - - name: one + - shared: true clientSelectors: - headers: - name: x-user-id diff --git a/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.jsonmerge.out.yaml b/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.jsonmerge.out.yaml index a679b04d1b..17f6d2cb0e 100644 --- a/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.jsonmerge.out.yaml +++ b/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.jsonmerge.out.yaml @@ -1,7 +1,7 @@ kind: BackendTrafficPolicy metadata: creationTimestamp: null - name: original + name: original-2 spec: rateLimit: global: @@ -23,7 +23,7 @@ spec: limit: requests: 21 unit: Hour - name: two + shared: false type: Global status: ancestors: null diff --git a/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.patch.yaml b/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.patch.yaml index 2d13bd03b0..40b6d91867 100644 --- a/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.patch.yaml +++ b/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.patch.yaml @@ -1,12 +1,13 @@ kind: BackendTrafficPolicy metadata: - name: original + name: original-2 spec: rateLimit: type: Global global: rules: - name: two + shared: false clientSelectors: - headers: - name: x-user-id diff --git a/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.strategicmerge.out.yaml b/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.strategicmerge.out.yaml index 1833716e3e..17f6d2cb0e 100644 --- a/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.strategicmerge.out.yaml +++ b/internal/utils/testdata/backendtrafficpolicy_ratelimitrule_merge.strategicmerge.out.yaml @@ -1,7 +1,7 @@ kind: BackendTrafficPolicy metadata: creationTimestamp: null - name: original + name: original-2 spec: rateLimit: global: @@ -23,25 +23,7 @@ spec: limit: requests: 21 unit: Hour - name: two - - clientSelectors: - - headers: - - name: x-user-id - type: Exact - value: one - cost: - request: - from: Number - number: 0 - response: - from: Metadata - metadata: - key: request_cost_set_by_ext_proc - namespace: io.envoyproxy.gateway.e2e - limit: - requests: 21 - unit: Hour - name: one + shared: false type: Global status: ancestors: null diff --git a/internal/xds/translator/ratelimit.go b/internal/xds/translator/ratelimit.go index 03b01298dd..e2d8ff8f77 100644 --- a/internal/xds/translator/ratelimit.go +++ b/internal/xds/translator/ratelimit.go @@ -63,15 +63,6 @@ func (t *Translator) patchHCMWithRateLimit(mgr *hcmv3.HttpConnectionManager, irL } } -// routeContainsGlobalRateLimit checks if a route has global rate limit configuration. -// Returns false if any required component is nil. -func routeContainsGlobalRateLimit(irRoute *ir.HTTPRoute) bool { - return irRoute != nil && - irRoute.Traffic != nil && - irRoute.Traffic.RateLimit != nil && - irRoute.Traffic.RateLimit.Global != nil -} - // isRateLimitPresent returns true if rate limit config exists for the listener. func (t *Translator) isRateLimitPresent(irListener *ir.HTTPListener) bool { // Return false if global ratelimiting is disabled. @@ -80,7 +71,7 @@ func (t *Translator) isRateLimitPresent(irListener *ir.HTTPListener) bool { } // Return true if rate limit config exists. for _, route := range irListener.Routes { - if routeContainsGlobalRateLimit(route) { + if isValidGlobalRateLimit(route) { return true } } @@ -88,42 +79,52 @@ func (t *Translator) isRateLimitPresent(irListener *ir.HTTPListener) bool { } // buildRateLimitFilter constructs a list of HTTP filters for rate limiting based on the provided HTTP listener configuration. -// It creates at most one filter per domain to avoid duplicates. +// It creates separate filters for shared and non-shared rate limits. func (t *Translator) buildRateLimitFilter(irListener *ir.HTTPListener) []*hcmv3.HttpFilter { if irListener == nil || irListener.Routes == nil { return nil } - var filters []*hcmv3.HttpFilter - // Map to track which domains we've already created filters for, prevents creating duplicate filters for the same domain - processedDomains := make(map[string]bool) + filters := []*hcmv3.HttpFilter{} + created := make(map[string]bool) - // Iterate over each route in the listener to create rate limit filters for shared rate limits. for _, route := range irListener.Routes { - if !routeContainsGlobalRateLimit(route) { + if !isValidGlobalRateLimit(route) { continue } - var domain string - filterName := getRateLimitFilterName(route) - if isSharedRateLimit(route) { - // For shared rate limits, use the domain derived from the traffic policy - domain = getDomainName(route) - } else { - // For non-shared rate limits, use the listener domain - domain = irListener.Name + hasShared, hasNonShared := false, false + var sharedRuleName string + + for _, rule := range route.Traffic.RateLimit.Global.Rules { + if isRuleShared(rule) { + hasShared = true + sharedRuleName = stripRuleIndexSuffix(rule.Name) + } else { + hasNonShared = true + } } - // Skip if we've already created a filter for this domain - if processedDomains[domain] { - continue + if hasShared { + sharedDomain := sharedRuleName + if !created[sharedDomain] { + filterName := fmt.Sprintf("%s/%s", egv1a1.EnvoyFilterRateLimit.String(), sharedRuleName) + if filter := createRateLimitFilter(t, irListener, sharedDomain, filterName); filter != nil { + filters = append(filters, filter) + } + created[sharedDomain] = true + } } - processedDomains[domain] = true - // Create a filter for this domain - filter := createRateLimitFilter(t, irListener, domain, filterName) - if filter != nil { - filters = append(filters, filter) + if hasNonShared { + nonSharedDomain := irListener.Name + if !created[nonSharedDomain] { + filterName := egv1a1.EnvoyFilterRateLimit.String() + if filter := createRateLimitFilter(t, irListener, nonSharedDomain, filterName); filter != nil { + filters = append(filters, filter) + } + created[nonSharedDomain] = true + } } } @@ -184,7 +185,7 @@ func createRateLimitFilter(t *Translator, irListener *ir.HTTPListener, domain, f func patchRouteWithRateLimit(route *routev3.Route, irRoute *ir.HTTPRoute) error { //nolint:unparam // Return early if no rate limit config exists. xdsRouteAction := route.GetRoute() - if !routeContainsGlobalRateLimit(irRoute) || xdsRouteAction == nil { + if !isValidGlobalRateLimit(irRoute) || xdsRouteAction == nil { return nil } rateLimits, costSpecified := buildRouteRateLimits(irRoute) @@ -223,42 +224,47 @@ func patchRouteWithRateLimitOnTypedFilterConfig(route *routev3.Route, rateLimits // buildRouteRateLimits constructs rate limit actions for a given route based on the global rate limit configuration. func buildRouteRateLimits(route *ir.HTTPRoute) (rateLimits []*routev3.RateLimit, costSpecified bool) { - descriptorPrefix := route.Name // Ensure route has rate limit config - if !routeContainsGlobalRateLimit(route) { + if !isValidGlobalRateLimit(route) { return nil, false } - // Determine if the rate limit is shared across multiple routes. + // Get the global rate limit configuration global := route.Traffic.RateLimit.Global - isShared := isSharedRateLimit(route) - - // Set the descriptor key based on whether the rate limit is shared. - var descriptorKey, descriptorValue string - if isShared { - // For shared rate limits, use BTP name as key and namespace as value - descriptorKey = route.Traffic.Name - descriptorValue = route.Traffic.Name - } else { - // For non-shared rate limits, include the route name in the descriptor - descriptorKey = getRouteDescriptor(descriptorPrefix) - descriptorValue = descriptorKey - } - - // Create a generic key action for the route descriptor. - routeDescriptor := &routev3.RateLimit_Action{ - ActionSpecifier: &routev3.RateLimit_Action_GenericKey_{ - GenericKey: &routev3.RateLimit_Action_GenericKey{ - DescriptorKey: descriptorKey, - DescriptorValue: descriptorValue, - }, - }, - } // Iterate over each rule in the global rate limit configuration. for rIdx, rule := range global.Rules { - // Initialize a list of rate limit actions for the current rule. - rlActions := []*routev3.RateLimit_Action{routeDescriptor} + // Create a list of rate limit actions for the current rule. + var rlActions []*routev3.RateLimit_Action + + // Create the route descriptor using the rule's shared attribute + var descriptorKey, descriptorValue string + if isRuleShared(rule) { + // For shared rule, use full rule name + descriptorKey = rule.Name + descriptorValue = rule.Name + } else { + // For non-shared rule, use route name in descriptor + descriptorKey = getRouteDescriptor(route.Name) + descriptorValue = descriptorKey + } + + // Create a generic key action for the route descriptor. + routeDescriptor := &routev3.RateLimit_Action{ + ActionSpecifier: &routev3.RateLimit_Action_GenericKey_{ + GenericKey: &routev3.RateLimit_Action_GenericKey{ + DescriptorKey: descriptorKey, + DescriptorValue: descriptorValue, + }, + }, + } + + // Add the generic key action + rlActions = append(rlActions, routeDescriptor) + + // Calculate the domain-specific rule index (0-based for each domain) + ruleIsShared := isRuleShared(rule) + domainRuleIdx := getDomainRuleIndex(global.Rules, rIdx, ruleIsShared) // Process each header match in the rule. for mIdx, match := range rule.HeaderMatches { @@ -266,7 +272,7 @@ func buildRouteRateLimits(route *ir.HTTPRoute) (rateLimits []*routev3.RateLimit, // Handle distinct matches by setting up request header actions. if match.Distinct { - descriptorKey := getRouteRuleDescriptor(rIdx, mIdx) + descriptorKey := getRouteRuleDescriptor(domainRuleIdx, mIdx) action = &routev3.RateLimit_Action{ ActionSpecifier: &routev3.RateLimit_Action_RequestHeaders_{ RequestHeaders: &routev3.RateLimit_Action_RequestHeaders{ @@ -277,8 +283,8 @@ func buildRouteRateLimits(route *ir.HTTPRoute) (rateLimits []*routev3.RateLimit, } } else { // Handle non-distinct matches by setting up header value match actions. - descriptorKey := getRouteRuleDescriptor(rIdx, mIdx) - descriptorVal := getRouteRuleDescriptor(rIdx, mIdx) + descriptorKey := getRouteRuleDescriptor(domainRuleIdx, mIdx) + descriptorVal := getRouteRuleDescriptor(domainRuleIdx, mIdx) headerMatcher := &routev3.HeaderMatcher{ Name: match.Name, HeaderMatchSpecifier: &routev3.HeaderMatcher_StringMatch{ @@ -358,8 +364,8 @@ func buildRouteRateLimits(route *ir.HTTPRoute) (rateLimits []*routev3.RateLimit, action := &routev3.RateLimit_Action{ ActionSpecifier: &routev3.RateLimit_Action_GenericKey_{ GenericKey: &routev3.RateLimit_Action_GenericKey{ - DescriptorKey: getRouteRuleDescriptor(rIdx, -1), - DescriptorValue: getRouteRuleDescriptor(rIdx, -1), + DescriptorKey: getRouteRuleDescriptor(domainRuleIdx, -1), + DescriptorValue: getRouteRuleDescriptor(domainRuleIdx, -1), }, }, } @@ -368,6 +374,7 @@ func buildRouteRateLimits(route *ir.HTTPRoute) (rateLimits []*routev3.RateLimit, // Create a rate limit object for the current rule. rateLimit := &routev3.RateLimit{Actions: rlActions} + if c := rule.RequestCost; c != nil { // Set the hits addend for the request cost if specified. rateLimit.HitsAddend = rateLimitCostToHitsAddend(c) @@ -423,43 +430,38 @@ func GetRateLimitServiceConfigStr(pbCfg *rlsconfv3.RateLimitConfig) (string, err // It returns a list of unique configurations, one for each domain needed across all listeners. // For shared rate limits, it ensures we only process each shared domain once to improve efficiency. func BuildRateLimitServiceConfig(irListeners []*ir.HTTPListener) []*rlsconfv3.RateLimitConfig { - // Map to store descriptors for each domain - domainDescriptors := make(map[string][]*rlsconfv3.RateLimitDescriptor) - // Map to track which domains we've already created filters for, prevents creating duplicate filters for the same domain - processedSharedDomains := make(map[string]bool) + // Map to store rate limit descriptors by domain name + domainDesc := make(map[string][]*rlsconfv3.RateLimitDescriptor) // Process each listener for _, irListener := range irListeners { - // Process each route to build descriptors + // Process each route in the listener for _, route := range irListener.Routes { - if !routeContainsGlobalRateLimit(route) { + // Skip routes without valid global rate limit configuration + if !isValidGlobalRateLimit(route) { continue } - domain := irListener.Name - if isSharedRateLimit(route) { - domain = getDomainName(route) - - // Skip if we've already processed this shared domain - if processedSharedDomains[domain] { - continue - } - processedSharedDomains[domain] = true - } + // Build all descriptors for this route in a single pass to maintain consistent indices + descriptors := buildRateLimitServiceDescriptors(route) - // Get route rule descriptors within each route - serviceDescriptors := buildRateLimitServiceDescriptors(route) - if len(serviceDescriptors) == 0 { + // Skip if no descriptors were created + if len(descriptors) == 0 { continue } - // Add the rate limit descriptor (handles both shared and non-shared) - addRateLimitDescriptor(route, serviceDescriptors, domain, domainDescriptors) + // Process shared rules - add to traffic policy domain + sharedDomain := getDomainSharedName(route) + addRateLimitDescriptor(route, descriptors, sharedDomain, domainDesc, true) + + // Process non-shared rules - add to listener-specific domain + listenerDomain := irListener.Name + addRateLimitDescriptor(route, descriptors, listenerDomain, domainDesc, false) } } - configs := createRateLimitConfigs(domainDescriptors) - return configs + // Convert domain descriptor map to list of rate limit configurations + return createRateLimitConfigs(domainDesc) } // createRateLimitConfigs creates rate limit configs from the domain descriptor map @@ -479,6 +481,25 @@ func createRateLimitConfigs( return configs } +// Helper to recursively compare two RateLimitDescriptors for equality +func descriptorsEqual(a, b *rlsconfv3.RateLimitDescriptor) bool { + if a == nil || b == nil { + return a == b + } + if a.Key != b.Key || a.Value != b.Value { + return false + } + if len(a.Descriptors) != len(b.Descriptors) { + return false + } + for i := range a.Descriptors { + if !descriptorsEqual(a.Descriptors[i], b.Descriptors[i]) { + return false + } + } + return true +} + // addRateLimitDescriptor adds rate limit descriptors to the domain descriptor map. // Handles both shared and route-specific rate limits. // @@ -495,31 +516,107 @@ func addRateLimitDescriptor( serviceDescriptors []*rlsconfv3.RateLimitDescriptor, domain string, domainDescriptors map[string][]*rlsconfv3.RateLimitDescriptor, + includeShared bool, ) { - var key, value string - - if isSharedRateLimit(route) { - // For shared rate limits, use traffic policy name key/value - key = route.Traffic.Name - value = route.Traffic.Name - } else { - // For non-shared rate limits, use route descriptor key/value - key = getRouteDescriptor(route.Name) - value = getRouteDescriptor(route.Name) + if !isValidGlobalRateLimit(route) || len(serviceDescriptors) == 0 { + return } - descriptor := &rlsconfv3.RateLimitDescriptor{ - Key: key, - Value: value, - Descriptors: serviceDescriptors, + for i, rule := range route.Traffic.RateLimit.Global.Rules { + if i >= len(serviceDescriptors) || (includeShared != isRuleShared(rule)) { + continue + } + + var descriptorKey string + if isRuleShared(rule) { + descriptorKey = rule.Name + } else { + descriptorKey = getRouteDescriptor(route.Name) + } + + // Find or create descriptor in domainDescriptors[domain] + var descriptorRule *rlsconfv3.RateLimitDescriptor + found := false + for _, d := range domainDescriptors[domain] { + if d.Key == descriptorKey { + descriptorRule = d + found = true + break + } + } + if !found { + descriptorRule = &rlsconfv3.RateLimitDescriptor{Key: descriptorKey, Value: descriptorKey} + domainDescriptors[domain] = append(domainDescriptors[domain], descriptorRule) + } + + // Ensure no duplicate descriptors + alreadyExists := false + for _, existing := range descriptorRule.Descriptors { + if descriptorsEqual(existing, serviceDescriptors[i]) { + alreadyExists = true + break + } + } + if !alreadyExists { + descriptorRule.Descriptors = append(descriptorRule.Descriptors, serviceDescriptors[i]) + } } - domainDescriptors[domain] = append(domainDescriptors[domain], descriptor) } -// Helper function to check if a route has a shared rate limit +// isSharedRateLimit checks if a route has at least one shared rate limit rule. +// It returns true if any rule in the global rate limit configuration is marked as shared. +// If no rules are shared or there's no global rate limit configuration, it returns false. func isSharedRateLimit(route *ir.HTTPRoute) bool { + if !isValidGlobalRateLimit(route) { + return false + } + global := route.Traffic.RateLimit.Global - return global != nil && global.Shared != nil && *global.Shared && len(global.Rules) > 0 + if len(global.Rules) == 0 { + return false + } + + // Check if any rule has shared=true + for _, rule := range global.Rules { + if isRuleShared(rule) { + return true + } + } + + return false +} + +// Helper function to check if a specific rule is shared +func isRuleShared(rule *ir.RateLimitRule) bool { + return rule != nil && rule.Shared != nil && *rule.Shared +} + +// Helper function to check if a specific rule in a route is shared +func isRuleAtIndexShared(route *ir.HTTPRoute, ruleIndex int) bool { + if route == nil || route.Traffic == nil || route.Traffic.RateLimit == nil || + route.Traffic.RateLimit.Global == nil || len(route.Traffic.RateLimit.Global.Rules) <= ruleIndex || ruleIndex < 0 { + return false + } + + return isRuleShared(route.Traffic.RateLimit.Global.Rules[ruleIndex]) +} + +// Helper function to map a global rule index to a domain-specific rule index +// This ensures that both shared and non-shared rules have indices starting from 0 in their own domains. +func getDomainRuleIndex(rules []*ir.RateLimitRule, globalRuleIdx int, ruleIsShared bool) int { + if globalRuleIdx < 0 || globalRuleIdx >= len(rules) { + return 0 + } + + // Count how many rules of the same "shared" status came before this one + count := 0 + for i := 0; i < globalRuleIdx; i++ { + // If we're looking for shared rules, count shared ones; otherwise count non-shared ones + if (ruleIsShared && isRuleShared(rules[i])) || (!ruleIsShared && !isRuleShared(rules[i])) { + count++ + } + } + return count } // buildRateLimitServiceDescriptors creates the rate limit service pb descriptors based on the global rate limit IR config. @@ -531,7 +628,6 @@ func buildRateLimitServiceDescriptors(route *ir.HTTPRoute) []*rlsconfv3.RateLimi global := route.Traffic.RateLimit.Global pbDescriptors := make([]*rlsconfv3.RateLimitDescriptor, 0, len(global.Rules)) - usedSharedKey := false // Track if the shared key has been used // The order in which matching descriptors are built is consistent with // the order in which ratelimit actions are built: @@ -549,17 +645,21 @@ func buildRateLimitServiceDescriptors(route *ir.HTTPRoute) []*rlsconfv3.RateLimi // We use a chain structure to describe the matching descriptors for one rule. var head, cur *rlsconfv3.RateLimitDescriptor + // Calculate the domain-specific rule index (0-based for each domain) + ruleIsShared := isRuleShared(rule) + domainRuleIdx := getDomainRuleIndex(global.Rules, rIdx, ruleIsShared) + // 1) Header Matches for mIdx, match := range rule.HeaderMatches { pbDesc := new(rlsconfv3.RateLimitDescriptor) // Distinct vs HeaderValueMatch if match.Distinct { // RequestHeader case - pbDesc.Key = getRouteRuleDescriptor(rIdx, mIdx) + pbDesc.Key = getRouteRuleDescriptor(domainRuleIdx, mIdx) } else { // HeaderValueMatch case - pbDesc.Key = getRouteRuleDescriptor(rIdx, mIdx) - pbDesc.Value = getRouteRuleDescriptor(rIdx, mIdx) + pbDesc.Key = getRouteRuleDescriptor(domainRuleIdx, mIdx) + pbDesc.Value = getRouteRuleDescriptor(domainRuleIdx, mIdx) } if mIdx == 0 { @@ -584,14 +684,14 @@ func buildRateLimitServiceDescriptors(route *ir.HTTPRoute) []*rlsconfv3.RateLimi // // An example of rate limit server configuration looks like this: // - // descriptors: - // - key: masked_remote_address //catch all the source IPs inside a CIDR - // value: 192.168.0.0/16 - // descriptors: - // - key: remote_address //set limit for individual IP - // rate_limit: - // unit: second - // requests_per_unit: 100 + // descriptors: + // - key: masked_remote_address //catch all the source IPs inside a CIDR + // value: 192.168.0.0/16 + // descriptors: + // - key: remote_address //set limit for individual IP + // rate_limit: + // unit: second + // requests_per_unit: 100 // // Please refer to [Rate Limit Service Descriptor list definition](https://github.com/envoyproxy/ratelimit#descriptor-list-definition) for details. // 2) CIDR Match @@ -617,22 +717,21 @@ func buildRateLimitServiceDescriptors(route *ir.HTTPRoute) []*rlsconfv3.RateLimi cur = pbDesc } } - // Case when both header and cidr match are not set and the ratelimit // will be applied to all traffic. // 3) No Match (apply to all traffic) if !rule.IsMatchSet() { pbDesc := new(rlsconfv3.RateLimitDescriptor) - // Determine if we should use the shared rate limit key (BTP-based) or a generic route key - if isSharedRateLimit(route) && !usedSharedKey { - // For shared rate limits, use BTP name and namespace - pbDesc.Key = route.Traffic.Name - pbDesc.Value = route.Traffic.Name - usedSharedKey = true + // Determine if we should use the shared rate limit key (rule-based) or a generic route key + if isRuleAtIndexShared(route, rIdx) { + // For shared rate limits, use rule name + pbDesc.Key = rule.Name + pbDesc.Value = rule.Name } else { - // Use generic key for non-shared rate limits - pbDesc.Key = getRouteRuleDescriptor(rIdx, -1) + // Use generic key for non-shared rate limits, with prefix for uniqueness + descriptorKey := getRouteRuleDescriptor(domainRuleIdx, -1) + pbDesc.Key = descriptorKey pbDesc.Value = pbDesc.Key } @@ -715,8 +814,9 @@ func (t *Translator) createRateLimitServiceCluster(tCtx *types.ResourceVersionTa }) } -func getDomainName(route *ir.HTTPRoute) string { - return strings.Replace(route.Traffic.Name, "/", "-", 1) +// getDomainSharedName returns the shared domain (stripped policy name), stripRuleIndexSuffix is used to remove the rule index suffix. +func getDomainSharedName(route *ir.HTTPRoute) string { + return stripRuleIndexSuffix(route.Traffic.RateLimit.Global.Rules[0].Name) } func getRouteRuleDescriptor(ruleIndex, matchIndex int) string { @@ -743,12 +843,37 @@ func (t *Translator) getRateLimitServiceGrpcHostPort() (string, uint32) { return u.Hostname(), uint32(p) } -// For shared rate limits, it appends the traffic policy name to the base filter name. +// getRateLimitFilterName gets the filter name for rate limits. +// If any rule in the route is shared, it appends the rule name to the base filter name. // For non-shared rate limits, it returns just the base filter name. +// Note: This function is primarily used for route-level filter configuration, not for HTTP filters at the listener level. func getRateLimitFilterName(route *ir.HTTPRoute) string { filterName := egv1a1.EnvoyFilterRateLimit.String() + // If any rule is shared, include the rule name in the filter name if isSharedRateLimit(route) { - filterName = fmt.Sprintf("%s/%s", filterName, route.Traffic.Name) + // Find the first shared rule to use its name + for _, rule := range route.Traffic.RateLimit.Global.Rules { + if isRuleShared(rule) { + filterName = fmt.Sprintf("%s/%s", filterName, stripRuleIndexSuffix(rule.Name)) + break + } + } } return filterName } + +// Helper to strip /rule/ from a rule name in order to use shared http filter +func stripRuleIndexSuffix(name string) string { + if i := strings.LastIndex(name, "/rule/"); i != -1 { + return name[:i] + } + return strings.Replace(name, "/", "-", 1) +} + +// Helper to check if a route has a valid global rate limit config +func isValidGlobalRateLimit(route *ir.HTTPRoute) bool { + return route != nil && + route.Traffic != nil && + route.Traffic.RateLimit != nil && + route.Traffic.RateLimit.Global != nil +} diff --git a/internal/xds/translator/testdata/in/ratelimit-config/distinct-match.yaml b/internal/xds/translator/testdata/in/ratelimit-config/distinct-match.yaml index 238ad7a6b1..7a9a6b0d18 100644 --- a/internal/xds/translator/testdata/in/ratelimit-config/distinct-match.yaml +++ b/internal/xds/translator/testdata/in/ratelimit-config/distinct-match.yaml @@ -10,11 +10,11 @@ http: routes: - name: "first-route" traffic: - name: "test-policy-1/test-namespace" rateLimit: global: rules: - - headerMatches: + - name: "test-namespace/test-policy-1/rule/0" + headerMatches: - name: "x-user-id" distinct: true limit: diff --git a/internal/xds/translator/testdata/in/ratelimit-config/distinct-remote-address-match.yaml b/internal/xds/translator/testdata/in/ratelimit-config/distinct-remote-address-match.yaml index 59b50eadb5..1ab9355d9f 100644 --- a/internal/xds/translator/testdata/in/ratelimit-config/distinct-remote-address-match.yaml +++ b/internal/xds/translator/testdata/in/ratelimit-config/distinct-remote-address-match.yaml @@ -10,11 +10,11 @@ http: routes: - name: "first-route" traffic: - name: "test-policy-1/test-namespace" rateLimit: global: rules: - - cidrMatch: + - name: "test-namespace/test-policy-1/rule/0" + cidrMatch: cidr: "192.168.0.0/16" ipv6: false maskLen: 16 diff --git a/internal/xds/translator/testdata/in/ratelimit-config/empty-header-matches.yaml b/internal/xds/translator/testdata/in/ratelimit-config/empty-header-matches.yaml index 55375b28c9..390285f565 100644 --- a/internal/xds/translator/testdata/in/ratelimit-config/empty-header-matches.yaml +++ b/internal/xds/translator/testdata/in/ratelimit-config/empty-header-matches.yaml @@ -10,11 +10,11 @@ http: routes: - name: "first-route" traffic: - name: "test-policy-1/test-namespace" rateLimit: global: rules: - - limit: + - name: "test-namespace/test-policy-1/rule/0" + limit: requests: 5 unit: second pathMatch: diff --git a/internal/xds/translator/testdata/in/ratelimit-config/global-shared-distinct-match.yaml b/internal/xds/translator/testdata/in/ratelimit-config/global-shared-distinct-match.yaml index 84a159a0f7..50929cd847 100644 --- a/internal/xds/translator/testdata/in/ratelimit-config/global-shared-distinct-match.yaml +++ b/internal/xds/translator/testdata/in/ratelimit-config/global-shared-distinct-match.yaml @@ -10,17 +10,17 @@ http: routes: - name: "first-route" traffic: - name: "test-policy-1/test-namespace" rateLimit: global: - shared: true rules: - - headerMatches: + - name: "test-namespace/test-policy-1/rule/0" + headerMatches: - name: "x-user-id" distinct: true limit: requests: 5 unit: second + shared: true pathMatch: exact: "foo/bar" destination: diff --git a/internal/xds/translator/testdata/in/ratelimit-config/global-shared-multiple-shared-policies.yaml b/internal/xds/translator/testdata/in/ratelimit-config/global-shared-multiple-shared-policies.yaml index a586d563df..a3271bb665 100644 --- a/internal/xds/translator/testdata/in/ratelimit-config/global-shared-multiple-shared-policies.yaml +++ b/internal/xds/translator/testdata/in/ratelimit-config/global-shared-multiple-shared-policies.yaml @@ -10,58 +10,58 @@ http: routes: - name: "first-route" traffic: - name: "test-policy-1/test-namespace" rateLimit: global: - shared: true rules: - - headerMatches: + - name: "test-namespace/test-policy-1/rule/0" + headerMatches: - name: "x-user-id" exact: "one" limit: requests: 5 unit: second + shared: true - name: "second-route" traffic: - name: "test-policy-1/test-namespace" rateLimit: global: - shared: true rules: - - headerMatches: + - name: "test-namespace/test-policy-1/rule/0" + headerMatches: - name: "x-user-id" exact: "one" limit: requests: 5 unit: second + shared: true pathMatch: exact: "foo/baz" - name: "third-route" traffic: - name: "test-policy-2/test-namespace" rateLimit: global: - shared: true rules: - - headerMatches: + - name: "test-namespace/test-policy-2/rule/0" + headerMatches: - name: "x-user-id" exact: "two" limit: requests: 10 unit: second + shared: true - name: "fourth-route" traffic: - name: "test-policy-3/test-namespace" rateLimit: global: - shared: false rules: - - headerMatches: + - name: "test-namespace/test-policy-3/rule/0" + headerMatches: - name: "x-user-id" exact: "three" limit: requests: 10 unit: second + shared: false pathMatch: exact: "foo/bar" destination: diff --git a/internal/xds/translator/testdata/in/ratelimit-config/header-and-cidr-matches.yaml b/internal/xds/translator/testdata/in/ratelimit-config/header-and-cidr-matches.yaml index 8849d5b9d9..65df64720d 100644 --- a/internal/xds/translator/testdata/in/ratelimit-config/header-and-cidr-matches.yaml +++ b/internal/xds/translator/testdata/in/ratelimit-config/header-and-cidr-matches.yaml @@ -10,11 +10,11 @@ http: routes: - name: "first-route" traffic: - name: "test-policy-1/test-namespace" rateLimit: global: rules: - - headerMatches: + - name: "test-namespace/test-policy-1/rule/0" + headerMatches: - name: "x-user-id" exact: "one" - name: "x-user-id" diff --git a/internal/xds/translator/testdata/in/ratelimit-config/masked-remote-address-match.yaml b/internal/xds/translator/testdata/in/ratelimit-config/masked-remote-address-match.yaml index 9756be4795..501c3b49d2 100644 --- a/internal/xds/translator/testdata/in/ratelimit-config/masked-remote-address-match.yaml +++ b/internal/xds/translator/testdata/in/ratelimit-config/masked-remote-address-match.yaml @@ -10,11 +10,11 @@ http: routes: - name: "first-route" traffic: - name: "test-policy-1/test-namespace" rateLimit: global: rules: - - cidrMatch: + - name: "test-namespace/test-policy-1/rule/0" + cidrMatch: cidr: "192.168.0.0/16" ipv6: false maskLen: 16 diff --git a/internal/xds/translator/testdata/in/ratelimit-config/multiple-domains.yaml b/internal/xds/translator/testdata/in/ratelimit-config/multiple-domains.yaml index 8321c3cbe0..87f60a62bb 100644 --- a/internal/xds/translator/testdata/in/ratelimit-config/multiple-domains.yaml +++ b/internal/xds/translator/testdata/in/ratelimit-config/multiple-domains.yaml @@ -10,27 +10,27 @@ http: routes: - name: "first-route" traffic: - name: "test-policy-1/test-namespace" rateLimit: global: - shared: true rules: - - headerMatches: + - name: "test-namespace/test-policy-1/rule/0" + headerMatches: - name: "x-user-id" exact: "one" limit: requests: 5 unit: second + shared: true - name: "second-route" traffic: - name: "test-policy-2/test-namespace" rateLimit: global: - shared: false rules: - - headerMatches: + - name: "test-namespace/test-policy-2/rule/0" + headerMatches: - name: "x-user-id" exact: "one" limit: requests: 5 unit: second + shared: false diff --git a/internal/xds/translator/testdata/in/ratelimit-config/multiple-global-shared-distinct-match.yaml b/internal/xds/translator/testdata/in/ratelimit-config/multiple-global-shared-distinct-match.yaml new file mode 100644 index 0000000000..54e33e635a --- /dev/null +++ b/internal/xds/translator/testdata/in/ratelimit-config/multiple-global-shared-distinct-match.yaml @@ -0,0 +1,47 @@ +http: +- name: "first-listener" + address: "0.0.0.0" + port: 10080 + hostnames: + - "*" + path: + mergeSlashes: true + escapedSlashesAction: UnescapeAndRedirect + routes: + - name: "first-route" + traffic: + rateLimit: + global: + rules: + - name: "test-namespace/test-policy-1/rule/0" + headerMatches: + - name: "x-user-id" + distinct: true + limit: + requests: 5 + unit: second + shared: true + - name: "test-namespace/test-policy-1/rule/1" + headerMatches: + - name: "x-user-id" + distinct: true + limit: + requests: 5 + unit: second + shared: true + - name: "test-namespace/test-policy-1/rule/2" + headerMatches: + - name: "x-user-id" + distinct: true + limit: + requests: 5 + unit: second + shared: true + pathMatch: + exact: "foo/bar" + destination: + name: "first-route-dest" + settings: + - endpoints: + - host: "1.2.3.4" + port: 50000 diff --git a/internal/xds/translator/testdata/in/ratelimit-config/multiple-listeners-distinct-match.yaml b/internal/xds/translator/testdata/in/ratelimit-config/multiple-listeners-distinct-match.yaml index 55dc7c79e0..ccc36f04aa 100644 --- a/internal/xds/translator/testdata/in/ratelimit-config/multiple-listeners-distinct-match.yaml +++ b/internal/xds/translator/testdata/in/ratelimit-config/multiple-listeners-distinct-match.yaml @@ -10,11 +10,11 @@ http: routes: - name: "first-route" traffic: - name: "test-policy-1/test-namespace" rateLimit: global: rules: - - headerMatches: + - name: "test-namespace/test-policy-1/rule/0" + headerMatches: - name: "x-user-id" distinct: true limit: @@ -39,11 +39,11 @@ http: routes: - name: "first-route" traffic: - name: "test-policy-2/test-namespace" rateLimit: global: rules: - - headerMatches: + - name: "test-namespace/test-policy-2/rule/0" + headerMatches: - name: "x-user-id" distinct: true limit: diff --git a/internal/xds/translator/testdata/in/ratelimit-config/multiple-listeners-same-shared-distinct-match.yaml b/internal/xds/translator/testdata/in/ratelimit-config/multiple-listeners-same-shared-distinct-match.yaml index c694042ab3..b37bc3220a 100644 --- a/internal/xds/translator/testdata/in/ratelimit-config/multiple-listeners-same-shared-distinct-match.yaml +++ b/internal/xds/translator/testdata/in/ratelimit-config/multiple-listeners-same-shared-distinct-match.yaml @@ -10,17 +10,17 @@ http: routes: - name: "first-route" traffic: - name: "test-policy-1/test-namespace" rateLimit: global: - shared: true rules: - - headerMatches: + - name: "test-namespace/test-policy-1/rule/0" + headerMatches: - name: "x-user-id" distinct: true limit: requests: 5 unit: second + shared: true pathMatch: exact: "foo/bar" destination: @@ -40,17 +40,17 @@ http: routes: - name: "first-route" traffic: - name: "test-policy-1/test-namespace" rateLimit: global: - shared: true rules: - - headerMatches: + - name: "test-namespace/test-policy-1/rule/0" + headerMatches: - name: "x-user-id" distinct: true limit: requests: 5 unit: second + shared: true pathMatch: exact: "foo/bar" destination: diff --git a/internal/xds/translator/testdata/in/ratelimit-config/multiple-listeners-shared-distinct-match.yaml b/internal/xds/translator/testdata/in/ratelimit-config/multiple-listeners-shared-distinct-match.yaml index b6d93f8b2d..9d45930773 100644 --- a/internal/xds/translator/testdata/in/ratelimit-config/multiple-listeners-shared-distinct-match.yaml +++ b/internal/xds/translator/testdata/in/ratelimit-config/multiple-listeners-shared-distinct-match.yaml @@ -10,11 +10,12 @@ http: routes: - name: "first-route" traffic: - name: "test-policy-1/test-namespace" + name: "test-namespace/test-policy-1" rateLimit: global: rules: - - headerMatches: + - name: "test-policy-1/rule/0" + headerMatches: - name: "x-user-id" distinct: true limit: @@ -39,17 +40,17 @@ http: routes: - name: "first-route" traffic: - name: "test-policy-2/test-namespace" rateLimit: global: - shared: true rules: - - headerMatches: + - name: "test-namespace/test-policy-2/rule/0" + headerMatches: - name: "x-user-id" distinct: true limit: requests: 5 unit: second + shared: true pathMatch: exact: "foo/bar" destination: diff --git a/internal/xds/translator/testdata/in/ratelimit-config/multiple-masked-remote-address-match-with-same-cidr.yaml b/internal/xds/translator/testdata/in/ratelimit-config/multiple-masked-remote-address-match-with-same-cidr.yaml index d988353203..fae7230914 100644 --- a/internal/xds/translator/testdata/in/ratelimit-config/multiple-masked-remote-address-match-with-same-cidr.yaml +++ b/internal/xds/translator/testdata/in/ratelimit-config/multiple-masked-remote-address-match-with-same-cidr.yaml @@ -10,18 +10,18 @@ http: routes: - name: "first-route" traffic: - name: "test-policy-1/test-namespace" rateLimit: global: - shared: false rules: - - cidrMatch: + - name: "test-namespace/test-policy-1/rule/0" + cidrMatch: cidr: "192.168.0.10/32" ipv6: false maskLen: 32 limit: requests: 15 unit: Hour + shared: false pathMatch: exact: "foo/bar" destination: @@ -32,18 +32,18 @@ http: port: 50000 - name: "second-route" traffic: - name: "test-policy-2/test-namespace" rateLimit: global: - shared: false rules: - - cidrMatch: + - name: "test-namespace/test-policy-2/rule/0" + cidrMatch: cidr: "192.168.0.10/32" ipv6: false maskLen: 32 limit: requests: 300 unit: Hour + shared: false pathMatch: exact: "foo/bar" destination: diff --git a/internal/xds/translator/testdata/in/ratelimit-config/multiple-matches.yaml b/internal/xds/translator/testdata/in/ratelimit-config/multiple-matches.yaml index 15a90087a6..2da0e13a50 100644 --- a/internal/xds/translator/testdata/in/ratelimit-config/multiple-matches.yaml +++ b/internal/xds/translator/testdata/in/ratelimit-config/multiple-matches.yaml @@ -10,12 +10,11 @@ http: routes: - name: "first-route" traffic: - name: "test-policy-1/test-namespace" rateLimit: global: - shared: false rules: - - headerMatches: + - name: "test-namespace/test-policy-1/rule/0" + headerMatches: - name: "x-user-id" exact: "one" - name: "x-user-id" @@ -23,6 +22,7 @@ http: limit: requests: 5 unit: second + shared: false pathMatch: exact: "foo/bar" destination: diff --git a/internal/xds/translator/testdata/in/ratelimit-config/multiple-routes.yaml b/internal/xds/translator/testdata/in/ratelimit-config/multiple-routes.yaml index 0a6a885c75..909b2388ef 100644 --- a/internal/xds/translator/testdata/in/ratelimit-config/multiple-routes.yaml +++ b/internal/xds/translator/testdata/in/ratelimit-config/multiple-routes.yaml @@ -10,12 +10,12 @@ http: routes: - name: "first-route" traffic: - name: "test-policy-1/test-namespace" rateLimit: global: shared: false rules: - - headerMatches: + - name: "test-namespace/test-policy-1/rule/0" + headerMatches: - name: "x-user-id" exact: "one" limit: @@ -25,12 +25,12 @@ http: exact: "foo/baz" - name: "second-route" traffic: - name: "test-policy-2/test-namespace" rateLimit: global: shared: false rules: - - headerMatches: + - name: "test-namespace/test-policy-2/rule/0" + headerMatches: - name: "x-user-id" exact: "two" limit: diff --git a/internal/xds/translator/testdata/in/ratelimit-config/multiple-rules.yaml b/internal/xds/translator/testdata/in/ratelimit-config/multiple-rules.yaml index 92817339d6..351dcf2fae 100644 --- a/internal/xds/translator/testdata/in/ratelimit-config/multiple-rules.yaml +++ b/internal/xds/translator/testdata/in/ratelimit-config/multiple-rules.yaml @@ -10,23 +10,25 @@ http: routes: - name: "first-route" traffic: - name: "test-policy-1/test-namespace" rateLimit: global: - shared: false rules: - - headerMatches: + - name: "test-namespace/test-policy-1/rule/0" + headerMatches: - name: "x-user-id" exact: "one" limit: requests: 5 unit: second - - headerMatches: + shared: false + - name: "test-namespace/test-policy-1/rule/1" + headerMatches: - name: "x-user-id" exact: "two" limit: requests: 10 unit: second + shared: false pathMatch: exact: "foo/bar" destination: diff --git a/internal/xds/translator/testdata/in/ratelimit-config/multiple-shared-and-unshared.yaml b/internal/xds/translator/testdata/in/ratelimit-config/multiple-shared-and-unshared.yaml new file mode 100644 index 0000000000..e70a5ecd8d --- /dev/null +++ b/internal/xds/translator/testdata/in/ratelimit-config/multiple-shared-and-unshared.yaml @@ -0,0 +1,47 @@ +http: +- name: "first-listener" + address: "0.0.0.0" + port: 10080 + hostnames: + - "*" + path: + mergeSlashes: true + escapedSlashesAction: UnescapeAndRedirect + routes: + - name: "first-route" + traffic: + rateLimit: + global: + rules: + - name: "test-namespace/test-policy-1/rule/0" + headerMatches: + - name: "x-user-id" + exact: "one" + limit: + requests: 5 + unit: second + shared: true + - name: "test-namespace/test-policy-1/rule/1" + headerMatches: + - name: "x-user-id" + exact: "two" + limit: + requests: 5 + unit: second + shared: false + - name: "test-namespace/test-policy-1/rule/2" + headerMatches: + - name: "x-user-id" + exact: "three" + limit: + requests: 5 + unit: second + shared: true + - name: "test-namespace/test-policy-1/rule/3" + headerMatches: + - name: "x-user-id" + exact: "four" + limit: + requests: 5 + unit: second + shared: false diff --git a/internal/xds/translator/testdata/in/ratelimit-config/multiple-shared-ratelimit-rules.yaml b/internal/xds/translator/testdata/in/ratelimit-config/multiple-shared-ratelimit-rules.yaml new file mode 100644 index 0000000000..77fc67e7a3 --- /dev/null +++ b/internal/xds/translator/testdata/in/ratelimit-config/multiple-shared-ratelimit-rules.yaml @@ -0,0 +1,47 @@ +http: +- name: "first-listener" + address: "0.0.0.0" + port: 10080 + hostnames: + - "*" + path: + mergeSlashes: true + escapedSlashesAction: UnescapeAndRedirect + routes: + - name: "first-route" + traffic: + rateLimit: + global: + rules: + - name: "test-namespace/test-policy-1/rule/0" + headerMatches: + - name: "x-user-id" + exact: "one" + limit: + requests: 1 + unit: second + shared: true + - name: "test-namespace/test-policy-1/rule/1" + headerMatches: + - name: "x-user-id" + exact: "two" + limit: + requests: 2 + unit: second + shared: true + - name: "test-namespace/test-policy-1/rule/2" + headerMatches: + - name: "x-user-id" + exact: "three" + limit: + requests: 3 + unit: second + shared: false + pathMatch: + exact: "foo/bar" + destination: + name: "first-route-dest" + settings: + - endpoints: + - host: "1.2.3.4" + port: 50000 diff --git a/internal/xds/translator/testdata/in/ratelimit-config/value-match.yaml b/internal/xds/translator/testdata/in/ratelimit-config/value-match.yaml index 2f27cd072f..2e0dcf9664 100644 --- a/internal/xds/translator/testdata/in/ratelimit-config/value-match.yaml +++ b/internal/xds/translator/testdata/in/ratelimit-config/value-match.yaml @@ -10,17 +10,17 @@ http: routes: - name: "first-route" traffic: - name: "test-policy-1/test-namespace" rateLimit: global: - shared: false rules: - - headerMatches: + - name: "test-namespace/test-policy-1/rule/0" + headerMatches: - name: "x-user-id" exact: "one" limit: requests: 5 unit: second + shared: false pathMatch: exact: "foo/bar" destination: diff --git a/internal/xds/translator/testdata/in/xds-ir/ratelimit-global-shared.yaml b/internal/xds/translator/testdata/in/xds-ir/ratelimit-global-shared.yaml index 8d59e27bc8..2b36d593e7 100644 --- a/internal/xds/translator/testdata/in/xds-ir/ratelimit-global-shared.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/ratelimit-global-shared.yaml @@ -13,17 +13,18 @@ http: - name: "first-route" hostname: "*" traffic: - name: "test-policy-1/test-namespace" + name: "test-namespace/test-policy-1" rateLimit: global: - shared: true rules: - - headerMatches: + - name: "test-namespace/test-policy-1/rule/0" + headerMatches: - name: "x-user-id" exact: "one" limit: requests: 5 unit: second + shared: true pathMatch: exact: "foo/bar" destination: @@ -37,17 +38,18 @@ http: - name: "second-route" hostname: "*" traffic: - name: "test-policy-1/test-namespace" + name: "test-namespace/test-policy-1" rateLimit: global: - shared: true rules: - - headerMatches: + - name: "test-namespace/test-policy-1/rule/0" + headerMatches: - name: "x-user-id" exact: "one" limit: requests: 5 unit: second + shared: true pathMatch: exact: "foo/baz" destination: @@ -61,17 +63,18 @@ http: - name: "third-route" hostname: "*" traffic: - name: "test-policy-2/test-namespace" + name: "test-namespace/test-policy-2" rateLimit: global: - shared: true rules: - - headerMatches: + - name: "test-namespace/test-policy-2/rule/0" + headerMatches: - name: "x-user-id" exact: "two" limit: requests: 10 unit: second + shared: true pathMatch: exact: "test" destination: @@ -85,17 +88,18 @@ http: - name: "fourth-route" hostname: "*" traffic: - name: "test-policy-3/test-namespace" + name: "test-namespace/test-policy-3" rateLimit: global: - shared: false rules: - - headerMatches: + - name: "test-namespace/test-policy-3/rule/0" + headerMatches: - name: "x-user-id" exact: "two" limit: requests: 10 unit: second + shared: false pathMatch: exact: "foo/bar" destination: diff --git a/internal/xds/translator/testdata/in/xds-ir/ratelimit-multi-global-shared.yaml b/internal/xds/translator/testdata/in/xds-ir/ratelimit-multi-global-shared.yaml new file mode 100644 index 0000000000..ebd57e1ad4 --- /dev/null +++ b/internal/xds/translator/testdata/in/xds-ir/ratelimit-multi-global-shared.yaml @@ -0,0 +1,94 @@ +metrics: + enablePerEndpointStats: true +http: + - name: "first-listener" + address: "::" + port: 10080 + hostnames: + - "*" + path: + mergeSlashes: true + escapedSlashesAction: UnescapeAndRedirect + routes: + - name: "first-route" + hostname: "*" + traffic: + name: "test-namespace/test-policy-1" + rateLimit: + global: + rules: + - name: "test-namespace/test-policy-1/rule/0" + headerMatches: + - name: "x-user-id" + exact: "one" + limit: + requests: 5 + unit: second + shared: true + pathMatch: + exact: "foo/bar" + destination: + name: "first-route-dest" + settings: + - endpoints: + - host: "1.2.3.4" + port: 50000 + name: "first-route-dest/backend/0" + + - name: "second-route" + hostname: "*" + traffic: + name: "test-namespace/test-policy-1" + rateLimit: + global: + rules: + - name: "test-namespace/test-policy-1/rule/0" + headerMatches: + - name: "x-user-id" + exact: "one" + limit: + requests: 5 + unit: second + shared: true + pathMatch: + exact: "foo/baz" + destination: + name: "second-route-dest" + settings: + - endpoints: + - host: "1.2.3.4" + port: 50000 + name: "second-route-dest/backend/0" + + - name: "third-route" + hostname: "*" + traffic: + name: "test-namespace/test-policy-2" + rateLimit: + global: + rules: + - name: "test-namespace/test-policy-2/rule/0" + headerMatches: + - name: "x-user-id" + exact: "two" + limit: + requests: 10 + unit: second + shared: true + - name: "test-namespace/test-policy-2/rule/1" + headerMatches: + - name: "x-user-id" + exact: "two" + limit: + requests: 10 + unit: second + shared: true + pathMatch: + exact: "test" + destination: + name: "third-route-dest" + settings: + - endpoints: + - host: "1.2.3.4" + port: 50000 + name: "third-route-dest/backend/0" diff --git a/internal/xds/translator/testdata/out/ratelimit-config/global-shared-distinct-match.yaml b/internal/xds/translator/testdata/out/ratelimit-config/global-shared-distinct-match.yaml index 62a2ad78f0..936dc97f4e 100644 --- a/internal/xds/translator/testdata/out/ratelimit-config/global-shared-distinct-match.yaml +++ b/internal/xds/translator/testdata/out/ratelimit-config/global-shared-distinct-match.yaml @@ -1,8 +1,8 @@ -name: test-policy-1-test-namespace -domain: test-policy-1-test-namespace +name: test-namespace/test-policy-1 +domain: test-namespace/test-policy-1 descriptors: - - key: test-policy-1/test-namespace - value: test-policy-1/test-namespace + - key: test-namespace/test-policy-1/rule/0 + value: test-namespace/test-policy-1/rule/0 rate_limit: null descriptors: - key: rule-0-match-0 diff --git a/internal/xds/translator/testdata/out/ratelimit-config/global-shared-multiple-shared-policies.yaml b/internal/xds/translator/testdata/out/ratelimit-config/global-shared-multiple-shared-policies.yaml index 83ebd9a9b9..5325d2fc06 100644 --- a/internal/xds/translator/testdata/out/ratelimit-config/global-shared-multiple-shared-policies.yaml +++ b/internal/xds/translator/testdata/out/ratelimit-config/global-shared-multiple-shared-policies.yaml @@ -19,11 +19,11 @@ descriptors: shadow_mode: false detailed_metric: false --- -name: test-policy-1-test-namespace -domain: test-policy-1-test-namespace +name: test-namespace/test-policy-1 +domain: test-namespace/test-policy-1 descriptors: - - key: test-policy-1/test-namespace - value: test-policy-1/test-namespace + - key: test-namespace/test-policy-1/rule/0 + value: test-namespace/test-policy-1/rule/0 rate_limit: null descriptors: - key: rule-0-match-0 @@ -40,11 +40,11 @@ descriptors: shadow_mode: false detailed_metric: false --- -name: test-policy-2-test-namespace -domain: test-policy-2-test-namespace +name: test-namespace/test-policy-2 +domain: test-namespace/test-policy-2 descriptors: - - key: test-policy-2/test-namespace - value: test-policy-2/test-namespace + - key: test-namespace/test-policy-2/rule/0 + value: test-namespace/test-policy-2/rule/0 rate_limit: null descriptors: - key: rule-0-match-0 diff --git a/internal/xds/translator/testdata/out/ratelimit-config/multiple-domains.yaml b/internal/xds/translator/testdata/out/ratelimit-config/multiple-domains.yaml index 0562531e11..af737a0a21 100644 --- a/internal/xds/translator/testdata/out/ratelimit-config/multiple-domains.yaml +++ b/internal/xds/translator/testdata/out/ratelimit-config/multiple-domains.yaml @@ -19,11 +19,11 @@ descriptors: shadow_mode: false detailed_metric: false --- -name: test-policy-1-test-namespace -domain: test-policy-1-test-namespace +name: test-namespace/test-policy-1 +domain: test-namespace/test-policy-1 descriptors: - - key: test-policy-1/test-namespace - value: test-policy-1/test-namespace + - key: test-namespace/test-policy-1/rule/0 + value: test-namespace/test-policy-1/rule/0 rate_limit: null descriptors: - key: rule-0-match-0 diff --git a/internal/xds/translator/testdata/out/ratelimit-config/multiple-global-shared-distinct-match.yaml b/internal/xds/translator/testdata/out/ratelimit-config/multiple-global-shared-distinct-match.yaml new file mode 100644 index 0000000000..82ec4f7081 --- /dev/null +++ b/internal/xds/translator/testdata/out/ratelimit-config/multiple-global-shared-distinct-match.yaml @@ -0,0 +1,54 @@ +name: test-namespace/test-policy-1 +domain: test-namespace/test-policy-1 +descriptors: + - key: test-namespace/test-policy-1/rule/0 + value: test-namespace/test-policy-1/rule/0 + rate_limit: null + descriptors: + - key: rule-0-match-0 + value: "" + rate_limit: + requests_per_unit: 5 + unit: SECOND + unlimited: false + name: "" + replaces: [] + descriptors: [] + shadow_mode: false + detailed_metric: false + shadow_mode: false + detailed_metric: false + - key: test-namespace/test-policy-1/rule/1 + value: test-namespace/test-policy-1/rule/1 + rate_limit: null + descriptors: + - key: rule-1-match-0 + value: "" + rate_limit: + requests_per_unit: 5 + unit: SECOND + unlimited: false + name: "" + replaces: [] + descriptors: [] + shadow_mode: false + detailed_metric: false + shadow_mode: false + detailed_metric: false + - key: test-namespace/test-policy-1/rule/2 + value: test-namespace/test-policy-1/rule/2 + rate_limit: null + descriptors: + - key: rule-2-match-0 + value: "" + rate_limit: + requests_per_unit: 5 + unit: SECOND + unlimited: false + name: "" + replaces: [] + descriptors: [] + shadow_mode: false + detailed_metric: false + shadow_mode: false + detailed_metric: false diff --git a/internal/xds/translator/testdata/out/ratelimit-config/multiple-listeners-same-shared-distinct-match.yaml b/internal/xds/translator/testdata/out/ratelimit-config/multiple-listeners-same-shared-distinct-match.yaml index 62a2ad78f0..936dc97f4e 100644 --- a/internal/xds/translator/testdata/out/ratelimit-config/multiple-listeners-same-shared-distinct-match.yaml +++ b/internal/xds/translator/testdata/out/ratelimit-config/multiple-listeners-same-shared-distinct-match.yaml @@ -1,8 +1,8 @@ -name: test-policy-1-test-namespace -domain: test-policy-1-test-namespace +name: test-namespace/test-policy-1 +domain: test-namespace/test-policy-1 descriptors: - - key: test-policy-1/test-namespace - value: test-policy-1/test-namespace + - key: test-namespace/test-policy-1/rule/0 + value: test-namespace/test-policy-1/rule/0 rate_limit: null descriptors: - key: rule-0-match-0 diff --git a/internal/xds/translator/testdata/out/ratelimit-config/multiple-listeners-shared-distinct-match.yaml b/internal/xds/translator/testdata/out/ratelimit-config/multiple-listeners-shared-distinct-match.yaml index 50b14f478a..e7379e4af6 100644 --- a/internal/xds/translator/testdata/out/ratelimit-config/multiple-listeners-shared-distinct-match.yaml +++ b/internal/xds/translator/testdata/out/ratelimit-config/multiple-listeners-shared-distinct-match.yaml @@ -19,11 +19,11 @@ descriptors: shadow_mode: false detailed_metric: false --- -name: test-policy-2-test-namespace -domain: test-policy-2-test-namespace +name: test-namespace/test-policy-2 +domain: test-namespace/test-policy-2 descriptors: - - key: test-policy-2/test-namespace - value: test-policy-2/test-namespace + - key: test-namespace/test-policy-2/rule/0 + value: test-namespace/test-policy-2/rule/0 rate_limit: null descriptors: - key: rule-0-match-0 diff --git a/internal/xds/translator/testdata/out/ratelimit-config/multiple-shared-and-unshared.yaml b/internal/xds/translator/testdata/out/ratelimit-config/multiple-shared-and-unshared.yaml new file mode 100644 index 0000000000..c02c4f20f8 --- /dev/null +++ b/internal/xds/translator/testdata/out/ratelimit-config/multiple-shared-and-unshared.yaml @@ -0,0 +1,69 @@ +name: first-listener +domain: first-listener +descriptors: + - key: first-route + value: first-route + rate_limit: null + descriptors: + - key: rule-0-match-0 + value: rule-0-match-0 + rate_limit: + requests_per_unit: 5 + unit: SECOND + unlimited: false + name: "" + replaces: [] + descriptors: [] + shadow_mode: false + detailed_metric: false + - key: rule-1-match-0 + value: rule-1-match-0 + rate_limit: + requests_per_unit: 5 + unit: SECOND + unlimited: false + name: "" + replaces: [] + descriptors: [] + shadow_mode: false + detailed_metric: false + shadow_mode: false + detailed_metric: false +--- +name: test-namespace/test-policy-1 +domain: test-namespace/test-policy-1 +descriptors: + - key: test-namespace/test-policy-1/rule/0 + value: test-namespace/test-policy-1/rule/0 + rate_limit: null + descriptors: + - key: rule-0-match-0 + value: rule-0-match-0 + rate_limit: + requests_per_unit: 5 + unit: SECOND + unlimited: false + name: "" + replaces: [] + descriptors: [] + shadow_mode: false + detailed_metric: false + shadow_mode: false + detailed_metric: false + - key: test-namespace/test-policy-1/rule/2 + value: test-namespace/test-policy-1/rule/2 + rate_limit: null + descriptors: + - key: rule-1-match-0 + value: rule-1-match-0 + rate_limit: + requests_per_unit: 5 + unit: SECOND + unlimited: false + name: "" + replaces: [] + descriptors: [] + shadow_mode: false + detailed_metric: false + shadow_mode: false + detailed_metric: false diff --git a/internal/xds/translator/testdata/out/ratelimit-config/multiple-shared-ratelimit-rules.yaml b/internal/xds/translator/testdata/out/ratelimit-config/multiple-shared-ratelimit-rules.yaml new file mode 100644 index 0000000000..ecb7e8ca89 --- /dev/null +++ b/internal/xds/translator/testdata/out/ratelimit-config/multiple-shared-ratelimit-rules.yaml @@ -0,0 +1,58 @@ +name: first-listener +domain: first-listener +descriptors: + - key: first-route + value: first-route + rate_limit: null + descriptors: + - key: rule-0-match-0 + value: rule-0-match-0 + rate_limit: + requests_per_unit: 3 + unit: SECOND + unlimited: false + name: "" + replaces: [] + descriptors: [] + shadow_mode: false + detailed_metric: false + shadow_mode: false + detailed_metric: false +--- +name: test-namespace/test-policy-1 +domain: test-namespace/test-policy-1 +descriptors: + - key: test-namespace/test-policy-1/rule/0 + value: test-namespace/test-policy-1/rule/0 + rate_limit: null + descriptors: + - key: rule-0-match-0 + value: rule-0-match-0 + rate_limit: + requests_per_unit: 1 + unit: SECOND + unlimited: false + name: "" + replaces: [] + descriptors: [] + shadow_mode: false + detailed_metric: false + shadow_mode: false + detailed_metric: false + - key: test-namespace/test-policy-1/rule/1 + value: test-namespace/test-policy-1/rule/1 + rate_limit: null + descriptors: + - key: rule-1-match-0 + value: rule-1-match-0 + rate_limit: + requests_per_unit: 2 + unit: SECOND + unlimited: false + name: "" + replaces: [] + descriptors: [] + shadow_mode: false + detailed_metric: false + shadow_mode: false + detailed_metric: false diff --git a/internal/xds/translator/testdata/out/xds-ir/ratelimit-global-shared.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/ratelimit-global-shared.listeners.yaml index 36c9cd5439..fd03d88dca 100644 --- a/internal/xds/translator/testdata/out/xds-ir/ratelimit-global-shared.listeners.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/ratelimit-global-shared.listeners.yaml @@ -14,20 +14,20 @@ initialStreamWindowSize: 65536 maxConcurrentStreams: 100 httpFilters: - - name: envoy.filters.http.ratelimit/test-policy-1/test-namespace + - name: envoy.filters.http.ratelimit/test-namespace/test-policy-1 typedConfig: '@type': type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit - domain: test-policy-1-test-namespace + domain: test-namespace/test-policy-1 enableXRatelimitHeaders: DRAFT_VERSION_03 rateLimitService: grpcService: envoyGrpc: clusterName: ratelimit_cluster transportApiVersion: V3 - - name: envoy.filters.http.ratelimit/test-policy-2/test-namespace + - name: envoy.filters.http.ratelimit/test-namespace/test-policy-2 typedConfig: '@type': type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit - domain: test-policy-2-test-namespace + domain: test-namespace/test-policy-2 enableXRatelimitHeaders: DRAFT_VERSION_03 rateLimitService: grpcService: diff --git a/internal/xds/translator/testdata/out/xds-ir/ratelimit-global-shared.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/ratelimit-global-shared.routes.yaml index 6eeb9bee6e..935fb21d8c 100644 --- a/internal/xds/translator/testdata/out/xds-ir/ratelimit-global-shared.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/ratelimit-global-shared.routes.yaml @@ -13,8 +13,8 @@ rateLimits: - actions: - genericKey: - descriptorKey: test-policy-1/test-namespace - descriptorValue: test-policy-1/test-namespace + descriptorKey: test-namespace/test-policy-1/rule/0 + descriptorValue: test-namespace/test-policy-1/rule/0 - headerValueMatch: descriptorKey: rule-0-match-0 descriptorValue: rule-0-match-0 @@ -33,8 +33,8 @@ rateLimits: - actions: - genericKey: - descriptorKey: test-policy-1/test-namespace - descriptorValue: test-policy-1/test-namespace + descriptorKey: test-namespace/test-policy-1/rule/0 + descriptorValue: test-namespace/test-policy-1/rule/0 - headerValueMatch: descriptorKey: rule-0-match-0 descriptorValue: rule-0-match-0 @@ -53,8 +53,8 @@ rateLimits: - actions: - genericKey: - descriptorKey: test-policy-2/test-namespace - descriptorValue: test-policy-2/test-namespace + descriptorKey: test-namespace/test-policy-2/rule/0 + descriptorValue: test-namespace/test-policy-2/rule/0 - headerValueMatch: descriptorKey: rule-0-match-0 descriptorValue: rule-0-match-0 diff --git a/internal/xds/translator/testdata/out/xds-ir/ratelimit-multi-global-shared.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/ratelimit-multi-global-shared.clusters.yaml new file mode 100644 index 0000000000..87afaadd1c --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/ratelimit-multi-global-shared.clusters.yaml @@ -0,0 +1,105 @@ +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: first-route-dest + ignoreHealthOnHostRemoval: true + lbPolicy: LEAST_REQUEST + name: first-route-dest + perConnectionBufferLimitBytes: 32768 + trackClusterStats: + perEndpointStats: true + type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: second-route-dest + ignoreHealthOnHostRemoval: true + lbPolicy: LEAST_REQUEST + name: second-route-dest + perConnectionBufferLimitBytes: 32768 + trackClusterStats: + perEndpointStats: true + type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: third-route-dest + ignoreHealthOnHostRemoval: true + lbPolicy: LEAST_REQUEST + name: third-route-dest + perConnectionBufferLimitBytes: 32768 + trackClusterStats: + perEndpointStats: true + type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + dnsRefreshRate: 30s + lbPolicy: LEAST_REQUEST + loadAssignment: + clusterName: ratelimit_cluster + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: envoy-ratelimit.envoy-gateway-system.svc.cluster.local + portValue: 8081 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: ratelimit_cluster/backend/-1 + name: ratelimit_cluster + perConnectionBufferLimitBytes: 32768 + respectDnsTtl: true + trackClusterStats: + perEndpointStats: true + transportSocket: + name: envoy.transport_sockets.tls + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + commonTlsContext: + tlsCertificates: + - certificateChain: + filename: /certs/tls.crt + privateKey: + filename: /certs/tls.key + validationContext: + trustedCa: + filename: /certs/ca.crt + type: STRICT_DNS + typedExtensionProtocolOptions: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + explicitHttpConfig: + http2ProtocolOptions: + initialConnectionWindowSize: 1048576 + initialStreamWindowSize: 65536 diff --git a/internal/xds/translator/testdata/out/xds-ir/ratelimit-multi-global-shared.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/ratelimit-multi-global-shared.endpoints.yaml new file mode 100644 index 0000000000..475b89a087 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/ratelimit-multi-global-shared.endpoints.yaml @@ -0,0 +1,36 @@ +- clusterName: first-route-dest + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.2.3.4 + portValue: 50000 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: first-route-dest/backend/0 +- clusterName: second-route-dest + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.2.3.4 + portValue: 50000 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: second-route-dest/backend/0 +- clusterName: third-route-dest + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.2.3.4 + portValue: 50000 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: third-route-dest/backend/0 diff --git a/internal/xds/translator/testdata/out/xds-ir/ratelimit-multi-global-shared.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/ratelimit-multi-global-shared.listeners.yaml new file mode 100644 index 0000000000..0e76130891 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/ratelimit-multi-global-shared.listeners.yaml @@ -0,0 +1,54 @@ +- address: + socketAddress: + address: '::' + portValue: 10080 + defaultFilterChain: + filters: + - name: envoy.filters.network.http_connection_manager + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + commonHttpProtocolOptions: + headersWithUnderscoresAction: REJECT_REQUEST + http2ProtocolOptions: + initialConnectionWindowSize: 1048576 + initialStreamWindowSize: 65536 + maxConcurrentStreams: 100 + httpFilters: + - name: envoy.filters.http.ratelimit/test-namespace/test-policy-1 + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit + domain: test-namespace/test-policy-1 + enableXRatelimitHeaders: DRAFT_VERSION_03 + rateLimitService: + grpcService: + envoyGrpc: + clusterName: ratelimit_cluster + transportApiVersion: V3 + - name: envoy.filters.http.ratelimit/test-namespace/test-policy-2 + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit + domain: test-namespace/test-policy-2 + enableXRatelimitHeaders: DRAFT_VERSION_03 + rateLimitService: + grpcService: + envoyGrpc: + clusterName: ratelimit_cluster + transportApiVersion: V3 + - name: envoy.filters.http.router + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + suppressEnvoyHeaders: true + mergeSlashes: true + normalizePath: true + pathWithEscapedSlashesAction: UNESCAPE_AND_REDIRECT + rds: + configSource: + ads: {} + resourceApiVersion: V3 + routeConfigName: first-listener + serverHeaderTransformation: PASS_THROUGH + statPrefix: http-10080 + useRemoteAddress: true + name: first-listener + name: first-listener + perConnectionBufferLimitBytes: 32768 diff --git a/internal/xds/translator/testdata/out/xds-ir/ratelimit-multi-global-shared.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/ratelimit-multi-global-shared.routes.yaml new file mode 100644 index 0000000000..9ebf1dd631 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/ratelimit-multi-global-shared.routes.yaml @@ -0,0 +1,79 @@ +- ignorePortInHostMatching: true + name: first-listener + virtualHosts: + - domains: + - '*' + name: first-listener/* + routes: + - match: + path: foo/bar + name: first-route + route: + cluster: first-route-dest + rateLimits: + - actions: + - genericKey: + descriptorKey: test-namespace/test-policy-1/rule/0 + descriptorValue: test-namespace/test-policy-1/rule/0 + - headerValueMatch: + descriptorKey: rule-0-match-0 + descriptorValue: rule-0-match-0 + expectMatch: true + headers: + - name: x-user-id + stringMatch: + exact: one + upgradeConfigs: + - upgradeType: websocket + - match: + path: foo/baz + name: second-route + route: + cluster: second-route-dest + rateLimits: + - actions: + - genericKey: + descriptorKey: test-namespace/test-policy-1/rule/0 + descriptorValue: test-namespace/test-policy-1/rule/0 + - headerValueMatch: + descriptorKey: rule-0-match-0 + descriptorValue: rule-0-match-0 + expectMatch: true + headers: + - name: x-user-id + stringMatch: + exact: one + upgradeConfigs: + - upgradeType: websocket + - match: + path: test + name: third-route + route: + cluster: third-route-dest + rateLimits: + - actions: + - genericKey: + descriptorKey: test-namespace/test-policy-2/rule/0 + descriptorValue: test-namespace/test-policy-2/rule/0 + - headerValueMatch: + descriptorKey: rule-0-match-0 + descriptorValue: rule-0-match-0 + expectMatch: true + headers: + - name: x-user-id + stringMatch: + exact: two + - actions: + - genericKey: + descriptorKey: test-namespace/test-policy-2/rule/1 + descriptorValue: test-namespace/test-policy-2/rule/1 + - headerValueMatch: + descriptorKey: rule-1-match-0 + descriptorValue: rule-1-match-0 + expectMatch: true + headers: + - name: x-user-id + stringMatch: + exact: two + upgradeConfigs: + - upgradeType: websocket diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 580c34083c..8e7da32400 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -3870,10 +3870,10 @@ _Appears in:_ | Field | Type | Required | Default | Description | | --- | --- | --- | --- | --- | -| `name` | _string_ | false | | Name is the name of the rule. This is used to identify the rule
in the Envoy configuration and as a unique identifier for merging. | | `clientSelectors` | _[RateLimitSelectCondition](#ratelimitselectcondition) array_ | false | | 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 no client selectors are specified, the rule applies to all traffic of
the targeted Route.
If the policy targets a Gateway, the rule applies to each Route of the Gateway.
Please note that each Route has its own rate limit counters. For example,
if a Gateway has two Routes, and the policy has a rule with limit 10rps,
each Route will have its own 10rps limit. | | `limit` | _[RateLimitValue](#ratelimitvalue)_ | true | | 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. | | `cost` | _[RateLimitCost](#ratelimitcost)_ | false | | Cost specifies the cost of requests and responses for the rule.
This is optional and if not specified, the default behavior is to reduce the rate limit counters by 1 on
the request path and do not reduce the rate limit counters on the response path. | +| `shared` | _boolean_ | false | | Shared determines whether this rate limit rule applies across all the policy targets.
If set to true, the rule is treated as a common bucket and is shared across all policy targets (xRoutes).
Default: false. | #### RateLimitSelectCondition diff --git a/test/e2e/testdata/ratelimit-global-shared-and-unshared-header-match.yaml b/test/e2e/testdata/ratelimit-global-shared-and-unshared-header-match.yaml new file mode 100644 index 0000000000..639bbd7257 --- /dev/null +++ b/test/e2e/testdata/ratelimit-global-shared-and-unshared-header-match.yaml @@ -0,0 +1,111 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: eg-rate-limit + namespace: gateway-conformance-infra +spec: + gatewayClassName: "{GATEWAY_CLASS_NAME}" + listeners: + - name: http-8080 + protocol: HTTP + port: 8080 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: header-ratelimit-1 + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: eg-rate-limit + rules: + - matches: + - path: + type: PathPrefix + value: /foo + backendRefs: + - name: infra-backend-v1 + port: 8080 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: header-ratelimit-2 + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: eg-rate-limit + rules: + - matches: + - path: + type: PathPrefix + value: /bar + backendRefs: + - name: infra-backend-v2 + port: 8080 +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: ratelimit-headers-route-policy + namespace: gateway-conformance-infra +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: header-ratelimit-1 + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: header-ratelimit-2 + mergeType: StrategicMerge + rateLimit: + type: Global + global: + rules: + - clientSelectors: + - headers: + - name: x-user-id + value: one + limit: + requests: 3 + unit: Hour + shared: true + - clientSelectors: + - headers: + - name: x-user-id + value: two + limit: + requests: 3 + unit: Hour + shared: false +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: ratelimit-headers-gateway-policy + namespace: gateway-conformance-infra +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: eg-rate-limit + rateLimit: + type: Global + global: + rules: + - clientSelectors: + - headers: + - name: x-user-id + value: three + limit: + requests: 3 + unit: Hour + shared: true + - clientSelectors: + - headers: + - name: x-user-id + value: four + limit: + requests: 3 + unit: Hour + shared: false diff --git a/test/e2e/testdata/ratelimit-global-shared-cidr-match.yaml b/test/e2e/testdata/ratelimit-global-shared-cidr-match.yaml index 68f620df8a..17871a2524 100644 --- a/test/e2e/testdata/ratelimit-global-shared-cidr-match.yaml +++ b/test/e2e/testdata/ratelimit-global-shared-cidr-match.yaml @@ -14,7 +14,6 @@ spec: rateLimit: type: Global global: - shared: true rules: - clientSelectors: - sourceCIDR: @@ -23,6 +22,7 @@ spec: limit: requests: 3 unit: Hour + shared: true --- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute diff --git a/test/e2e/testdata/ratelimit-global-shared-gateway-header-match.yaml b/test/e2e/testdata/ratelimit-global-shared-gateway-header-match.yaml index 04ff67b4b5..95fd22ad79 100644 --- a/test/e2e/testdata/ratelimit-global-shared-gateway-header-match.yaml +++ b/test/e2e/testdata/ratelimit-global-shared-gateway-header-match.yaml @@ -57,7 +57,6 @@ spec: rateLimit: type: Global global: - shared: true rules: - clientSelectors: - headers: @@ -67,3 +66,4 @@ spec: limit: requests: 3 unit: Hour + shared: true diff --git a/test/e2e/tests/ratelimit.go b/test/e2e/tests/ratelimit.go index da8cff9fdc..b3522af687 100644 --- a/test/e2e/tests/ratelimit.go +++ b/test/e2e/tests/ratelimit.go @@ -41,6 +41,7 @@ func init() { ConformanceTests = append(ConformanceTests, UsageRateLimitTest) ConformanceTests = append(ConformanceTests, RateLimitGlobalSharedCidrMatchTest) ConformanceTests = append(ConformanceTests, RateLimitGlobalSharedGatewayHeaderMatchTest) + ConformanceTests = append(ConformanceTests, RateLimitGlobalMergeTest) } var RateLimitCIDRMatchTest = suite.ConformanceTest{ @@ -946,6 +947,89 @@ var RateLimitGlobalSharedGatewayHeaderMatchTest = suite.ConformanceTest{ }, } +var RateLimitGlobalMergeTest = suite.ConformanceTest{ + ShortName: "RateLimitGlobalMergeTest", + Description: "Limit requests with matching headers across multiple routes, verifying both shared and unshared rate limit behaviors", + Manifests: []string{"testdata/ratelimit-global-shared-and-unshared-header-match.yaml"}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + ns := "gateway-conformance-infra" + route1NN := types.NamespacedName{Name: "header-ratelimit-1", Namespace: ns} + route2NN := types.NamespacedName{Name: "header-ratelimit-2", Namespace: ns} + gwNN := types.NamespacedName{Name: "eg-rate-limit", Namespace: ns} + + gwAddr1 := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), route1NN) + gwAddr2 := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), route2NN) + + t.Run("shared_route_policy_x-user-id=one", func(t *testing.T) { + headers := map[string]string{"x-user-id": "one"} + + expectOk1 := http.ExpectedResponse{Request: http.Request{Path: "/bar", Headers: headers}, Response: http.Response{StatusCode: 200}, Namespace: ns} + expectOk2 := http.ExpectedResponse{Request: http.Request{Path: "/foo", Headers: headers}, Response: http.Response{StatusCode: 200}, Namespace: ns} + expectOk3 := http.ExpectedResponse{Request: http.Request{Path: "/bar", Headers: headers}, Response: http.Response{StatusCode: 200}, Namespace: ns} + expectLimit := http.ExpectedResponse{Request: http.Request{Path: "/foo", Headers: headers}, Response: http.Response{StatusCode: 429}, Namespace: ns} + + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr2, expectOk1) + + _ = GotExactExpectedResponse(t, 1, suite.RoundTripper, http.MakeRequest(t, &expectOk1, gwAddr2, "HTTP", "http"), expectOk1) + _ = GotExactExpectedResponse(t, 1, suite.RoundTripper, http.MakeRequest(t, &expectOk2, gwAddr1, "HTTP", "http"), expectOk2) + _ = GotExactExpectedResponse(t, 1, suite.RoundTripper, http.MakeRequest(t, &expectOk3, gwAddr2, "HTTP", "http"), expectOk3) + _ = GotExactExpectedResponse(t, 1, suite.RoundTripper, http.MakeRequest(t, &expectLimit, gwAddr1, "HTTP", "http"), expectLimit) + }) + + t.Run("unshared_route_policy_x-user-id=two", func(t *testing.T) { + headers := map[string]string{"x-user-id": "two"} + + // Route 1 (/foo) + ok1 := http.ExpectedResponse{Request: http.Request{Path: "/foo", Headers: headers}, Response: http.Response{StatusCode: 200}, Namespace: ns} + limit1 := http.ExpectedResponse{Request: http.Request{Path: "/foo", Headers: headers}, Response: http.Response{StatusCode: 429}, Namespace: ns} + // Route 2 (/bar) + ok2 := http.ExpectedResponse{Request: http.Request{Path: "/bar", Headers: headers}, Response: http.Response{StatusCode: 200}, Namespace: ns} + limit2 := http.ExpectedResponse{Request: http.Request{Path: "/bar", Headers: headers}, Response: http.Response{StatusCode: 429}, Namespace: ns} + + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr1, ok1) + + _ = GotExactExpectedResponse(t, 3, suite.RoundTripper, http.MakeRequest(t, &ok1, gwAddr1, "HTTP", "http"), ok1) + _ = GotExactExpectedResponse(t, 1, suite.RoundTripper, http.MakeRequest(t, &limit1, gwAddr1, "HTTP", "http"), limit1) + + _ = GotExactExpectedResponse(t, 3, suite.RoundTripper, http.MakeRequest(t, &ok2, gwAddr2, "HTTP", "http"), ok2) + _ = GotExactExpectedResponse(t, 1, suite.RoundTripper, http.MakeRequest(t, &limit2, gwAddr2, "HTTP", "http"), limit2) + }) + + t.Run("shared_gateway_policy_x-user-id=three", func(t *testing.T) { + headers := map[string]string{"x-user-id": "three"} + + ok1 := http.ExpectedResponse{Request: http.Request{Path: "/bar", Headers: headers}, Response: http.Response{StatusCode: 200}, Namespace: ns} + ok2 := http.ExpectedResponse{Request: http.Request{Path: "/foo", Headers: headers}, Response: http.Response{StatusCode: 200}, Namespace: ns} + ok3 := http.ExpectedResponse{Request: http.Request{Path: "/bar", Headers: headers}, Response: http.Response{StatusCode: 200}, Namespace: ns} + limit := http.ExpectedResponse{Request: http.Request{Path: "/foo", Headers: headers}, Response: http.Response{StatusCode: 429}, Namespace: ns} + + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr2, ok1) + + _ = GotExactExpectedResponse(t, 1, suite.RoundTripper, http.MakeRequest(t, &ok1, gwAddr2, "HTTP", "http"), ok1) + _ = GotExactExpectedResponse(t, 1, suite.RoundTripper, http.MakeRequest(t, &ok2, gwAddr1, "HTTP", "http"), ok2) + _ = GotExactExpectedResponse(t, 1, suite.RoundTripper, http.MakeRequest(t, &ok3, gwAddr2, "HTTP", "http"), ok3) + _ = GotExactExpectedResponse(t, 1, suite.RoundTripper, http.MakeRequest(t, &limit, gwAddr1, "HTTP", "http"), limit) + }) + + t.Run("unshared_gateway_policy__x-user-id=four", func(t *testing.T) { + headers := map[string]string{"x-user-id": "four"} + + ok1 := http.ExpectedResponse{Request: http.Request{Path: "/foo", Headers: headers}, Response: http.Response{StatusCode: 200}, Namespace: ns} + limit1 := http.ExpectedResponse{Request: http.Request{Path: "/foo", Headers: headers}, Response: http.Response{StatusCode: 429}, Namespace: ns} + ok2 := http.ExpectedResponse{Request: http.Request{Path: "/bar", Headers: headers}, Response: http.Response{StatusCode: 200}, Namespace: ns} + limit2 := http.ExpectedResponse{Request: http.Request{Path: "/bar", Headers: headers}, Response: http.Response{StatusCode: 429}, Namespace: ns} + + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr1, ok1) + + _ = GotExactExpectedResponse(t, 3, suite.RoundTripper, http.MakeRequest(t, &ok1, gwAddr1, "HTTP", "http"), ok1) + _ = GotExactExpectedResponse(t, 1, suite.RoundTripper, http.MakeRequest(t, &limit1, gwAddr1, "HTTP", "http"), limit1) + + _ = GotExactExpectedResponse(t, 3, suite.RoundTripper, http.MakeRequest(t, &ok2, gwAddr2, "HTTP", "http"), ok2) + _ = GotExactExpectedResponse(t, 1, suite.RoundTripper, http.MakeRequest(t, &limit2, gwAddr2, "HTTP", "http"), limit2) + }) + }, +} + func GotExactExpectedResponse(t *testing.T, n int, r roundtripper.RoundTripper, req roundtripper.Request, resp http.ExpectedResponse) error { for i := 0; i < n; i++ { cReq, cRes, err := r.CaptureRoundTrip(req) diff --git a/test/helm/gateway-crds-helm/all.out.yaml b/test/helm/gateway-crds-helm/all.out.yaml index be47c7f6c9..2612431a81 100644 --- a/test/helm/gateway-crds-helm/all.out.yaml +++ b/test/helm/gateway-crds-helm/all.out.yaml @@ -18570,23 +18570,17 @@ spec: - requests - unit type: object - name: + shared: description: |- - Name is the name of the rule. This is used to identify the rule - in the Envoy configuration and as a unique identifier for merging. - type: string + Shared determines whether this rate limit rule applies across all the policy targets. + If set to true, the rule is treated as a common bucket and is shared across all policy targets (xRoutes). + Default: false. + type: boolean required: - limit type: object maxItems: 64 type: array - shared: - default: false - description: |- - Shared determines whether the rate limit rules apply across all the policy targets. - If set to true, the rule is treated as a common bucket and is shared across all policy targets (xRoutes). - Default: false. - type: boolean required: - rules type: object @@ -18825,11 +18819,12 @@ spec: - requests - unit type: object - name: + shared: description: |- - Name is the name of the rule. This is used to identify the rule - in the Envoy configuration and as a unique identifier for merging. - type: string + Shared determines whether this rate limit rule applies across all the policy targets. + If set to true, the rule is treated as a common bucket and is shared across all policy targets (xRoutes). + Default: false. + type: boolean required: - limit type: object diff --git a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml index a0e7257100..9dc657ce74 100644 --- a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml +++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml @@ -1258,23 +1258,17 @@ spec: - requests - unit type: object - name: + shared: description: |- - Name is the name of the rule. This is used to identify the rule - in the Envoy configuration and as a unique identifier for merging. - type: string + Shared determines whether this rate limit rule applies across all the policy targets. + If set to true, the rule is treated as a common bucket and is shared across all policy targets (xRoutes). + Default: false. + type: boolean required: - limit type: object maxItems: 64 type: array - shared: - default: false - description: |- - Shared determines whether the rate limit rules apply across all the policy targets. - If set to true, the rule is treated as a common bucket and is shared across all policy targets (xRoutes). - Default: false. - type: boolean required: - rules type: object @@ -1513,11 +1507,12 @@ spec: - requests - unit type: object - name: + shared: description: |- - Name is the name of the rule. This is used to identify the rule - in the Envoy configuration and as a unique identifier for merging. - type: string + Shared determines whether this rate limit rule applies across all the policy targets. + If set to true, the rule is treated as a common bucket and is shared across all policy targets (xRoutes). + Default: false. + type: boolean required: - limit type: object From 6cd7760143113819fb630083f754bf1c6ae7929f Mon Sep 17 00:00:00 2001 From: Arko Dasgupta Date: Tue, 13 May 2025 12:05:23 -0700 Subject: [PATCH 65/66] fix make gen Signed-off-by: Arko Dasgupta --- .../envoy-gateway-gateway-namespace-config-watch.out.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/helm/gateway-helm/envoy-gateway-gateway-namespace-config-watch.out.yaml b/test/helm/gateway-helm/envoy-gateway-gateway-namespace-config-watch.out.yaml index a288660e2f..c466c17162 100644 --- a/test/helm/gateway-helm/envoy-gateway-gateway-namespace-config-watch.out.yaml +++ b/test/helm/gateway-helm/envoy-gateway-gateway-namespace-config-watch.out.yaml @@ -40,7 +40,7 @@ data: type: GatewayNamespace rateLimitDeployment: container: - image: docker.io/envoyproxy/ratelimit:master + image: docker.io/envoyproxy/ratelimit:3e085e5b patch: type: StrategicMerge value: From 3ab349d2f643d1ed1837b2513cbb4b11900b6f55 Mon Sep 17 00:00:00 2001 From: Arko Dasgupta Date: Tue, 13 May 2025 12:19:32 -0700 Subject: [PATCH 66/66] make gen round 2 Signed-off-by: Arko Dasgupta --- .../proxy/testdata/gateway-namespace-mode/deployment.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/infrastructure/kubernetes/proxy/testdata/gateway-namespace-mode/deployment.yaml b/internal/infrastructure/kubernetes/proxy/testdata/gateway-namespace-mode/deployment.yaml index ba240a1208..5522170230 100644 --- a/internal/infrastructure/kubernetes/proxy/testdata/gateway-namespace-mode/deployment.yaml +++ b/internal/infrastructure/kubernetes/proxy/testdata/gateway-namespace-mode/deployment.yaml @@ -269,7 +269,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-dev + image: docker.io/envoyproxy/envoy:distroless-v1.34.0 imagePullPolicy: IfNotPresent lifecycle: preStop: @@ -713,7 +713,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.name - image: docker.io/envoyproxy/envoy:distroless-dev + image: docker.io/envoyproxy/envoy:distroless-v1.34.0 imagePullPolicy: IfNotPresent lifecycle: preStop: